1 //====== Copyright Valve Corporation, All rights reserved. ====================
2
3 #include <time.h>
4
5 #include <steam/isteamnetworkingsockets.h>
6 #include "steamnetworkingsockets_connections.h"
7 #include "steamnetworkingsockets_lowlevel.h"
8 #include "../steamnetworkingsockets_certstore.h"
9 #include "csteamnetworkingsockets.h"
10 #include "crypto.h"
11
12 #include "tier0/memdbgoff.h"
13
14 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_DIAGNOSTICSUI
15 #include "../../common/steammessages_gamenetworkingui.pb.h"
16 #endif
17
18 // memdbgon must be the last include file in a .cpp file!!!
19 #include "tier0/memdbgon.h"
20
21 #ifdef __GNUC__
22 // error: assuming signed overflow does not occur when assuming that (X + c) < X is always false [-Werror=strict-overflow]
23 // current steamrt:scout gcc "g++ (SteamRT 4.8.4-1ubuntu15~12.04+steamrt1.2+srt1) 4.8.4" requires this at the top due to optimizations
24 #pragma GCC diagnostic ignored "-Wstrict-overflow"
25 #endif
26
27 // Put everything in a namespace, so we don't violate the one definition rule
28 namespace SteamNetworkingSocketsLib {
29
30 const int k_nMaxRecentLocalConnectionIDs = 256;
31 static CUtlVectorFixed<uint16,k_nMaxRecentLocalConnectionIDs> s_vecRecentLocalConnectionIDs;
32
33 /// Check if we've sent a "spam reply", meaning a reply to an incoming
34 /// message that could be random spoofed garbage. Returns false if we've
35 /// recently sent one and cannot send any more right now without risking
36 /// being taken advantage of. Returns true if we haven't sent too many
37 /// such packets recently, and it's OK to send one now. (If true is returned,
38 /// it's assumed that you will send one.)
BCheckGlobalSpamReplyRateLimit(SteamNetworkingMicroseconds usecNow)39 bool BCheckGlobalSpamReplyRateLimit( SteamNetworkingMicroseconds usecNow )
40 {
41 static SteamNetworkingMicroseconds s_usecLastSpamReplySent;
42 if ( s_usecLastSpamReplySent + k_nMillion/4 > usecNow )
43 return false;
44 s_usecLastSpamReplySent = usecNow;
45 return true;
46 }
47
48 // Hack code used to generate C++ code to add a new CA key to the table above
49 //void KludgePrintPublicKey()
50 //{
51 // CECSigningPublicKey key;
52 // char *x = strdup( "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJrsoE4XUc5iaNVpACyh4fobLbwm02tOo6AIOtNygpuE" );
53 // DbgVerify( key.LoadFromAndWipeBuffer( x, strlen(x) ) );
54 // CUtlStringBuilder bufText;
55 // for ( uint32 i = 0 ; i < key.GetLength() ; ++i )
56 // {
57 // bufText.AppendFormat("\\x%02x", key.GetData()[i] );
58 // }
59 // SHA256Digest_t digest;
60 // CCrypto::GenerateSHA256Digest( key.GetData(), key.GetLength(), &digest );
61 // SpewWarning( "TrustedKey( %llullu, \"%s\" )\n", LittleQWord( *(uint64*)&digest ), bufText.String() );
62 //}
63
64 /////////////////////////////////////////////////////////////////////////////
65 //
66 // Message storage
67 //
68 /////////////////////////////////////////////////////////////////////////////
69
DefaultFreeData(SteamNetworkingMessage_t * pMsg)70 void CSteamNetworkingMessage::DefaultFreeData( SteamNetworkingMessage_t *pMsg )
71 {
72 free( pMsg->m_pData );
73 }
74
75
ReleaseFunc(SteamNetworkingMessage_t * pIMsg)76 void CSteamNetworkingMessage::ReleaseFunc( SteamNetworkingMessage_t *pIMsg )
77 {
78 CSteamNetworkingMessage *pMsg = static_cast<CSteamNetworkingMessage *>( pIMsg );
79
80 // Free up the buffer, if we have one
81 if ( pMsg->m_pData && pMsg->m_pfnFreeData )
82 (*pMsg->m_pfnFreeData)( pMsg );
83 pMsg->m_pData = nullptr; // Just for grins
84
85 // We must not currently be in any queue. In fact, our parent
86 // might have been destroyed.
87 Assert( !pMsg->m_links.m_pQueue );
88 Assert( !pMsg->m_links.m_pPrev );
89 Assert( !pMsg->m_links.m_pNext );
90 Assert( !pMsg->m_linksSecondaryQueue.m_pQueue );
91 Assert( !pMsg->m_linksSecondaryQueue.m_pPrev );
92 Assert( !pMsg->m_linksSecondaryQueue.m_pNext );
93
94 // Self destruct
95 // FIXME Should avoid this dynamic memory call with some sort of pooling
96 delete pMsg;
97 }
98
New(uint32 cbSize)99 CSteamNetworkingMessage *CSteamNetworkingMessage::New( uint32 cbSize )
100 {
101 // FIXME Should avoid this dynamic memory call with some sort of pooling
102 CSteamNetworkingMessage *pMsg = new CSteamNetworkingMessage;
103
104 // NOTE: Intentionally not memsetting the whole thing;
105 // this struct is pretty big.
106
107 // Allocate buffer if requested
108 if ( cbSize )
109 {
110 pMsg->m_pData = malloc( cbSize );
111 if ( pMsg->m_pData == nullptr )
112 {
113 delete pMsg;
114 SpewError( "Failed to allocate %d-byte message buffer", cbSize );
115 return nullptr;
116 }
117 pMsg->m_cbSize = cbSize;
118 pMsg->m_pfnFreeData = CSteamNetworkingMessage::DefaultFreeData;
119 }
120 else
121 {
122 pMsg->m_cbSize = 0;
123 pMsg->m_pData = nullptr;
124 pMsg->m_pfnFreeData = nullptr;
125 }
126
127 // Clear identity
128 pMsg->m_conn = k_HSteamNetConnection_Invalid;
129 pMsg->m_identityPeer.m_eType = k_ESteamNetworkingIdentityType_Invalid;
130 pMsg->m_identityPeer.m_cbSize = 0;
131
132 // Set the release function
133 pMsg->m_pfnRelease = ReleaseFunc;
134
135 // Clear these fields
136 pMsg->m_nChannel = -1;
137 pMsg->m_nFlags = 0;
138 pMsg->m_links.Clear();
139 pMsg->m_linksSecondaryQueue.Clear();
140
141 return pMsg;
142 }
143
New(CSteamNetworkConnectionBase * pParent,uint32 cbSize,int64 nMsgNum,int nFlags,SteamNetworkingMicroseconds usecNow)144 CSteamNetworkingMessage *CSteamNetworkingMessage::New( CSteamNetworkConnectionBase *pParent, uint32 cbSize, int64 nMsgNum, int nFlags, SteamNetworkingMicroseconds usecNow )
145 {
146 CSteamNetworkingMessage *pMsg = New( cbSize );
147 if ( !pMsg )
148 {
149 // Failed! if it's for a reliable message, then we must abort the connection.
150 // If unreliable message....well we've spewed, but let's try to keep on chugging.
151 if ( pParent && ( nFlags & k_nSteamNetworkingSend_Reliable ) )
152 pParent->ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_InternalError, "Failed to allocate buffer to receive reliable message" );
153 return nullptr;
154 }
155
156 if ( pParent )
157 {
158 pMsg->m_identityPeer = pParent->m_identityRemote;
159 pMsg->m_conn = pParent->m_hConnectionSelf;
160 pMsg->m_nConnUserData = pParent->GetUserData();
161 }
162 pMsg->m_usecTimeReceived = usecNow;
163 pMsg->m_nMessageNumber = nMsgNum;
164 pMsg->m_nFlags = nFlags;
165
166 return pMsg;
167 }
168
LinkBefore(CSteamNetworkingMessage * pSuccessor,Links CSteamNetworkingMessage::* pMbrLinks,SteamNetworkingMessageQueue * pQueue)169 void CSteamNetworkingMessage::LinkBefore( CSteamNetworkingMessage *pSuccessor, Links CSteamNetworkingMessage::*pMbrLinks, SteamNetworkingMessageQueue *pQueue )
170 {
171 // Make sure we're not already in a queue
172 UnlinkFromQueue( pMbrLinks );
173
174 // No successor?
175 if ( !pSuccessor )
176 {
177 LinkToQueueTail( pMbrLinks, pQueue );
178 return;
179 }
180
181 // Otherwise, the queue cannot be empty, since it at least contains the successor
182 pQueue->AssertLockHeld();
183 Assert( pQueue->m_pFirst );
184 Assert( pQueue->m_pLast );
185 Assert( (pSuccessor->*pMbrLinks).m_pQueue == pQueue );
186
187 CSteamNetworkingMessage *pPrev = (pSuccessor->*pMbrLinks).m_pPrev;
188 if ( pPrev )
189 {
190 Assert( pQueue->m_pFirst != pSuccessor );
191 Assert( (pPrev->*pMbrLinks).m_pNext == pSuccessor );
192 Assert( (pPrev->*pMbrLinks).m_pQueue == pQueue );
193
194 (pPrev->*pMbrLinks).m_pNext = this;
195 (this->*pMbrLinks).m_pPrev = pPrev;
196 }
197 else
198 {
199 Assert( pQueue->m_pFirst == pSuccessor );
200 pQueue->m_pFirst = this;
201 (this->*pMbrLinks).m_pPrev = nullptr; // Should already be null, but let's slam it again anyway
202 }
203
204 // Finish up
205 (this->*pMbrLinks).m_pQueue = pQueue;
206 (this->*pMbrLinks).m_pNext = pSuccessor;
207 (pSuccessor->*pMbrLinks).m_pPrev = this;
208 }
209
LinkToQueueTail(Links CSteamNetworkingMessage::* pMbrLinks,SteamNetworkingMessageQueue * pQueue)210 void CSteamNetworkingMessage::LinkToQueueTail( Links CSteamNetworkingMessage::*pMbrLinks, SteamNetworkingMessageQueue *pQueue )
211 {
212 pQueue->AssertLockHeld();
213
214 // Locate previous link that should point to us.
215 // Does the queue have anything in it?
216 if ( pQueue->m_pLast )
217 {
218 Assert( pQueue->m_pFirst );
219 Assert( !(pQueue->m_pLast->*pMbrLinks).m_pNext );
220 (pQueue->m_pLast->*pMbrLinks).m_pNext = this;
221 }
222 else
223 {
224 Assert( !pQueue->m_pFirst );
225 pQueue->m_pFirst = this;
226 }
227
228 // Link back to the previous guy, if any
229 (this->*pMbrLinks).m_pPrev = pQueue->m_pLast;
230
231 // We're last in the list, nobody after us
232 (this->*pMbrLinks).m_pNext = nullptr;
233 pQueue->m_pLast = this;
234
235 // Remember what queue we're in
236 (this->*pMbrLinks).m_pQueue = pQueue;
237 }
238
UnlinkFromQueue(Links CSteamNetworkingMessage::* pMbrLinks)239 void CSteamNetworkingMessage::UnlinkFromQueue( Links CSteamNetworkingMessage::*pMbrLinks )
240 {
241 Links &links = this->*pMbrLinks;
242 if ( links.m_pQueue == nullptr )
243 return;
244 SteamNetworkingMessageQueue &q = *links.m_pQueue;
245 q.AssertLockHeld();
246
247 // Unlink from previous
248 if ( links.m_pPrev )
249 {
250 Assert( q.m_pFirst != this );
251 Assert( (links.m_pPrev->*pMbrLinks).m_pNext == this );
252 (links.m_pPrev->*pMbrLinks).m_pNext = links.m_pNext;
253 }
254 else
255 {
256 Assert( q.m_pFirst == this );
257 q.m_pFirst = links.m_pNext;
258 }
259
260 // Unlink from next
261 if ( links.m_pNext )
262 {
263 Assert( q.m_pLast != this );
264 Assert( (links.m_pNext->*pMbrLinks).m_pPrev == this );
265 (links.m_pNext->*pMbrLinks).m_pPrev = links.m_pPrev;
266 }
267 else
268 {
269 Assert( q.m_pLast == this );
270 q.m_pLast = links.m_pPrev;
271 }
272
273 // Clear links
274 links.m_pQueue = nullptr;
275 links.m_pPrev = nullptr;
276 links.m_pNext = nullptr;
277 }
278
Unlink()279 void CSteamNetworkingMessage::Unlink()
280 {
281 // Unlink from any queues we are in
282 UnlinkFromQueue( &CSteamNetworkingMessage::m_links );
283 UnlinkFromQueue( &CSteamNetworkingMessage::m_linksSecondaryQueue );
284 }
285
286 ShortDurationLock g_lockAllRecvMessageQueues( "all_recv_msg_queue" );
287
AssertLockHeld() const288 void SteamNetworkingMessageQueue::AssertLockHeld() const
289 {
290 if ( m_pRequiredLock )
291 m_pRequiredLock->AssertHeldByCurrentThread();
292 }
293
PurgeMessages()294 void SteamNetworkingMessageQueue::PurgeMessages()
295 {
296
297 AssertLockHeld();
298 while ( !empty() )
299 {
300 CSteamNetworkingMessage *pMsg = m_pFirst;
301 pMsg->Unlink();
302 Assert( m_pFirst != pMsg );
303 pMsg->Release();
304 }
305 }
306
RemoveMessages(SteamNetworkingMessage_t ** ppOutMessages,int nMaxMessages)307 int SteamNetworkingMessageQueue::RemoveMessages( SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages )
308 {
309 int nMessagesReturned = 0;
310 AssertLockHeld();
311
312 while ( !empty() && nMessagesReturned < nMaxMessages )
313 {
314 // Locate message, put into caller's list
315 CSteamNetworkingMessage *pMsg = m_pFirst;
316 ppOutMessages[nMessagesReturned++] = pMsg;
317
318 // Unlink from all queues
319 pMsg->Unlink();
320
321 // That should have unlinked from *us*, so it shouldn't be in our queue anymore
322 Assert( m_pFirst != pMsg );
323 }
324
325 return nMessagesReturned;
326 }
327
328 /////////////////////////////////////////////////////////////////////////////
329 //
330 // CSteamNetworkPollGroup
331 //
332 /////////////////////////////////////////////////////////////////////////////
333
CSteamNetworkPollGroup(CSteamNetworkingSockets * pInterface)334 CSteamNetworkPollGroup::CSteamNetworkPollGroup( CSteamNetworkingSockets *pInterface )
335 : m_pSteamNetworkingSocketsInterface( pInterface )
336 , m_hPollGroupSelf( k_HSteamListenSocket_Invalid )
337 {
338 // Object creation is rare; to keep things simple we require the global lock
339 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
340
341 m_queueRecvMessages.m_pRequiredLock = &g_lockAllRecvMessageQueues;
342 }
343
~CSteamNetworkPollGroup()344 CSteamNetworkPollGroup::~CSteamNetworkPollGroup()
345 {
346 // Object deletion is rare; to keep things simple we require the global lock
347 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
348 m_lock.AssertHeldByCurrentThread();
349
350 FOR_EACH_VEC_BACK( m_vecConnections, i )
351 {
352 CSteamNetworkConnectionBase *pConn = m_vecConnections[i];
353 ConnectionScopeLock connectionLock( *pConn ); // NOTE: It's OK to take more than one lock here, because we hold the global lock
354 Assert( pConn->m_pPollGroup == this );
355 pConn->RemoveFromPollGroup();
356 Assert( m_vecConnections.Count() == i );
357 }
358
359 // We should not have any messages now! but if we do, unlink them
360 {
361 ShortDurationScopeLock lockMessageQueues( g_lockAllRecvMessageQueues );
362 Assert( m_queueRecvMessages.empty() );
363
364 // But if we do, unlink them but leave them in the main queue.
365 while ( !m_queueRecvMessages.empty() )
366 {
367 CSteamNetworkingMessage *pMsg = m_queueRecvMessages.m_pFirst;
368
369 // The poll group queue is the "secondary queue"
370 Assert( pMsg->m_linksSecondaryQueue.m_pQueue == &m_queueRecvMessages );
371
372 // They should be in some other queue (for the connection) as the main queue.
373 // That owns them and make sure they get deleted!
374 Assert( pMsg->m_links.m_pQueue != nullptr );
375
376 // OK, do the work
377 pMsg->UnlinkFromQueue( &CSteamNetworkingMessage::m_linksSecondaryQueue );
378
379 // Make sure it worked.
380 Assert( pMsg != m_queueRecvMessages.m_pFirst );
381 }
382 }
383
384 // Remove us from global table, if we're in it
385 if ( m_hPollGroupSelf != k_HSteamNetPollGroup_Invalid )
386 {
387 g_tables_lock.AssertHeldByCurrentThread();
388 int idx = m_hPollGroupSelf & 0xffff;
389 if ( g_mapPollGroups.IsValidIndex( idx ) && g_mapPollGroups[ idx ] == this )
390 {
391 g_mapPollGroups[ idx ] = nullptr; // Just for grins
392 g_mapPollGroups.RemoveAt( idx );
393 }
394 else
395 {
396 AssertMsg( false, "Poll group handle bookkeeping bug!" );
397 }
398
399 m_hPollGroupSelf = k_HSteamNetPollGroup_Invalid;
400 }
401
402 // Unlock, so our lock debugging doesn't complain.
403 // Here we assume that we're only locked once
404 m_lock.unlock();
405 }
406
AssignHandleAndAddToGlobalTable()407 void CSteamNetworkPollGroup::AssignHandleAndAddToGlobalTable()
408 {
409 // Object creation is rare; to keep things simple we require the global lock
410 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
411 g_tables_lock.AssertHeldByCurrentThread();
412
413 Assert( m_hPollGroupSelf == k_HSteamNetPollGroup_Invalid );
414
415 // We actually don't do map "lookups". We assume the number of listen sockets
416 // is going to be reasonably small.
417 static int s_nDummy;
418 ++s_nDummy;
419 int idx = g_mapPollGroups.Insert( s_nDummy, this );
420 Assert( idx < 0x1000 );
421
422 // Use upper 15 bits as a connection sequence number, so that listen socket handles
423 // are not reused within a short time period.
424 // (The top bit is reserved, so that listen socket handles and poll group handles
425 // come from a different namespace, so that we can immediately detect using the wrong
426 // and make that bug more obvious.)
427 static uint32 s_nUpperBits = 0;
428 s_nUpperBits += 0x10000;
429 if ( s_nUpperBits & 0x10000000 )
430 s_nUpperBits = 0x10000;
431
432 // Set the handle
433 m_hPollGroupSelf = HSteamNetPollGroup( idx | s_nUpperBits | 0x80000000 );
434 }
435
436
437 /////////////////////////////////////////////////////////////////////////////
438 //
439 // CSteamNetworkListenSocketBase
440 //
441 /////////////////////////////////////////////////////////////////////////////
442
CSteamNetworkListenSocketBase(CSteamNetworkingSockets * pSteamNetworkingSocketsInterface)443 CSteamNetworkListenSocketBase::CSteamNetworkListenSocketBase( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface )
444 : m_pSteamNetworkingSocketsInterface( pSteamNetworkingSocketsInterface )
445 , m_hListenSocketSelf( k_HSteamListenSocket_Invalid )
446 {
447 m_connectionConfig.Init( &pSteamNetworkingSocketsInterface->m_connectionConfig );
448 }
449
~CSteamNetworkListenSocketBase()450 CSteamNetworkListenSocketBase::~CSteamNetworkListenSocketBase()
451 {
452 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
453 AssertMsg( m_mapChildConnections.Count() == 0, "Destroy() not used properly" );
454
455 // Remove us from global table, if we're in it
456 if ( m_hListenSocketSelf != k_HSteamListenSocket_Invalid )
457 {
458 TableScopeLock tableScopeLock( g_tables_lock ); // NOTE: We can do this since listen sockets are protected by the global lock, not an object lock
459
460 int idx = m_hListenSocketSelf & 0xffff;
461 if ( g_mapListenSockets.IsValidIndex( idx ) && g_mapListenSockets[ idx ] == this )
462 {
463 g_mapListenSockets[ idx ] = nullptr; // Just for grins
464 g_mapListenSockets.RemoveAt( idx );
465 }
466 else
467 {
468 AssertMsg( false, "Listen socket handle bookkeeping bug!" );
469 }
470
471 m_hListenSocketSelf = k_HSteamListenSocket_Invalid;
472 }
473
474 #ifdef STEAMNETWORKINGSOCKETS_STEAMCLIENT
475 Assert( !m_pLegacyPollGroup ); // Should have been cleaned up by Destroy()
476 #endif
477 }
478
BInitListenSocketCommon(int nOptions,const SteamNetworkingConfigValue_t * pOptions,SteamDatagramErrMsg & errMsg)479 bool CSteamNetworkListenSocketBase::BInitListenSocketCommon( int nOptions, const SteamNetworkingConfigValue_t *pOptions, SteamDatagramErrMsg &errMsg )
480 {
481 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
482
483 Assert( m_hListenSocketSelf == k_HSteamListenSocket_Invalid );
484
485 // Assign us a handle, and add us to the global table
486 {
487
488 // We actually don't do map "lookups". We assume the number of listen sockets
489 // is going to be reasonably small.
490 static int s_nDummy;
491 ++s_nDummy;
492 int idx = g_mapListenSockets.Insert( s_nDummy, this );
493 Assert( idx < 0x1000 );
494
495 // Use upper 15 bits as a connection sequence number, so that listen socket handles
496 // are not reused within a short time period.
497 // (The top bit is reserved, so that listen socket handles and poll group handles
498 // come from a different namespace, so that we can immediately detect using the wrong
499 // and make that bug more obvious.)
500 static uint32 s_nUpperBits = 0;
501 s_nUpperBits += 0x10000;
502 if ( s_nUpperBits & 0x10000000 )
503 s_nUpperBits = 0x10000;
504
505 // Add it to our table of listen sockets
506 m_hListenSocketSelf = HSteamListenSocket( idx | s_nUpperBits );
507 }
508
509 // Set options, if any
510 if ( pOptions )
511 {
512 for ( int i = 0 ; i < nOptions ; ++i )
513 {
514 if ( !m_pSteamNetworkingSocketsInterface->m_pSteamNetworkingUtils->SetConfigValueStruct( pOptions[i], k_ESteamNetworkingConfig_ListenSocket, m_hListenSocketSelf ) )
515 {
516 V_sprintf_safe( errMsg, "Error setting option %d", pOptions[i].m_eValue );
517 return false;
518 }
519 }
520 }
521 else if ( nOptions != 0 )
522 {
523 V_strcpy_safe( errMsg, "Options list is NULL, but nOptions != 0?" );
524 return false;
525 }
526
527 // Check if symmetric is enabled, then make sure it's supported.
528 // It cannot be changed after listen socket creation
529 m_connectionConfig.m_SymmetricConnect.Lock();
530 if ( BSymmetricMode() )
531 {
532 if ( !BSupportsSymmetricMode() )
533 {
534 V_strcpy_safe( errMsg, "Symmetric mode not supported" );
535 return false;
536 }
537 }
538
539 // OK
540 return true;
541 }
542
Destroy()543 void CSteamNetworkListenSocketBase::Destroy()
544 {
545 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
546
547 // Destroy all child connections
548 FOR_EACH_HASHMAP( m_mapChildConnections, h )
549 {
550 CSteamNetworkConnectionBase *pChild = m_mapChildConnections[ h ];
551 ConnectionScopeLock connectionLock( *pChild );
552 Assert( pChild->m_pParentListenSocket == this );
553 Assert( pChild->m_hSelfInParentListenSocketMap == h );
554
555 int n = m_mapChildConnections.Count();
556 pChild->ConnectionQueueDestroy();
557 Assert( m_mapChildConnections.Count() == n-1 );
558 }
559
560 #ifdef STEAMNETWORKINGSOCKETS_STEAMCLIENT
561 if ( m_pLegacyPollGroup )
562 {
563 m_pLegacyPollGroup->m_lock.lock(); // Don't use scope object. It will unlock when we destruct
564 m_pLegacyPollGroup.reset();
565 }
566 #endif
567
568 // Self destruct
569 delete this;
570 }
571
APIGetAddress(SteamNetworkingIPAddr * pAddress)572 bool CSteamNetworkListenSocketBase::APIGetAddress( SteamNetworkingIPAddr *pAddress )
573 {
574 // Base class doesn't know
575 return false;
576 }
577
BSupportsSymmetricMode()578 bool CSteamNetworkListenSocketBase::BSupportsSymmetricMode()
579 {
580 return false;
581 }
582
BAddChildConnection(CSteamNetworkConnectionBase * pConn,SteamNetworkingErrMsg & errMsg)583 bool CSteamNetworkListenSocketBase::BAddChildConnection( CSteamNetworkConnectionBase *pConn, SteamNetworkingErrMsg &errMsg )
584 {
585 pConn->AssertLocksHeldByCurrentThread();
586
587 // Safety check
588 if ( pConn->m_pParentListenSocket || pConn->m_hSelfInParentListenSocketMap != -1 || pConn->m_hConnectionSelf != k_HSteamNetConnection_Invalid )
589 {
590 Assert( pConn->m_pParentListenSocket == nullptr );
591 Assert( pConn->m_hSelfInParentListenSocketMap == -1 );
592 Assert( pConn->m_hConnectionSelf == k_HSteamNetConnection_Invalid );
593 V_sprintf_safe( errMsg, "Cannot add child connection - connection already has a parent or is in connection map?" );
594 return false;
595 }
596
597 if ( pConn->m_identityRemote.IsInvalid() || !pConn->m_unConnectionIDRemote )
598 {
599 Assert( !pConn->m_identityRemote.IsInvalid() );
600 Assert( pConn->m_unConnectionIDRemote );
601 V_sprintf_safe( errMsg, "Cannot add child connection - connection not initialized with remote identity/ConnID" );
602 return false;
603 }
604
605 RemoteConnectionKey_t key{ pConn->m_identityRemote, pConn->m_unConnectionIDRemote };
606 if ( m_mapChildConnections.Find( key ) != m_mapChildConnections.InvalidIndex() )
607 {
608 V_sprintf_safe( errMsg, "Duplicate child connection! %s %u", SteamNetworkingIdentityRender( pConn->m_identityRemote ).c_str(), pConn->m_unConnectionIDRemote );
609 AssertMsg1( false, "%s", errMsg );
610 return false;
611 }
612
613 // Setup linkage
614 pConn->m_pParentListenSocket = this;
615 pConn->m_hSelfInParentListenSocketMap = m_mapChildConnections.Insert( key, pConn );
616 pConn->m_bConnectionInitiatedRemotely = true;
617
618 // Connection configuration will inherit from us
619 pConn->m_connectionConfig.Init( &m_connectionConfig );
620
621 // If we are possibly providing an old interface that did not have poll groups,
622 // add the connection to the default poll group. (But note that certain use cases,
623 // e.g. custom signaling, the poll group may have already been assigned by the app code.
624 // Don't override it, if so.)
625 #ifdef STEAMNETWORKINGSOCKETS_STEAMCLIENT
626 if ( !pConn->m_pPollGroup )
627 {
628 if ( !m_pLegacyPollGroup )
629 m_pLegacyPollGroup.reset( new CSteamNetworkPollGroup( m_pSteamNetworkingSocketsInterface ) );
630 pConn->SetPollGroup( m_pLegacyPollGroup.get() );
631 }
632 #endif
633
634 return true;
635 }
636
AboutToDestroyChildConnection(CSteamNetworkConnectionBase * pConn)637 void CSteamNetworkListenSocketBase::AboutToDestroyChildConnection( CSteamNetworkConnectionBase *pConn )
638 {
639 Assert( pConn->m_pParentListenSocket == this );
640 int hChild = pConn->m_hSelfInParentListenSocketMap;
641
642 pConn->m_pParentListenSocket = nullptr;
643 pConn->m_hSelfInParentListenSocketMap = -1;
644
645 if ( m_mapChildConnections[ hChild ] == pConn )
646 {
647 m_mapChildConnections[ hChild ] = nullptr; // just for kicks
648 m_mapChildConnections.RemoveAt( hChild );
649 }
650 else
651 {
652 AssertMsg( false, "Listen socket child list corruption!" );
653 FOR_EACH_HASHMAP( m_mapChildConnections, h )
654 {
655 if ( m_mapChildConnections[h] == pConn )
656 m_mapChildConnections.RemoveAt(h);
657 }
658 }
659 }
660
661 /////////////////////////////////////////////////////////////////////////////
662 //
663 // Abstract connection classes
664 //
665 /////////////////////////////////////////////////////////////////////////////
666
CSteamNetworkConnectionBase(CSteamNetworkingSockets * pSteamNetworkingSocketsInterface,ConnectionScopeLock & scopeLock)667 CSteamNetworkConnectionBase::CSteamNetworkConnectionBase( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface, ConnectionScopeLock &scopeLock )
668 : ILockableThinker( m_defaultLock )
669 , m_pSteamNetworkingSocketsInterface( pSteamNetworkingSocketsInterface )
670 {
671 m_hConnectionSelf = k_HSteamNetConnection_Invalid;
672 m_eConnectionState = k_ESteamNetworkingConnectionState_None;
673 m_eConnectionWireState = k_ESteamNetworkingConnectionState_None;
674 m_usecWhenEnteredConnectionState = 0;
675 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_DIAGNOSTICSUI
676 m_usecWhenNextDiagnosticsUpdate = k_nThinkTime_Never;
677 #endif
678 m_usecWhenSentConnectRequest = 0;
679 m_usecWhenCreated = 0;
680 m_ulHandshakeRemoteTimestamp = 0;
681 m_usecWhenReceivedHandshakeRemoteTimestamp = 0;
682 m_eEndReason = k_ESteamNetConnectionEnd_Invalid;
683 m_szEndDebug[0] = '\0';
684 memset( &m_identityLocal, 0, sizeof(m_identityLocal) );
685 memset( &m_identityRemote, 0, sizeof(m_identityRemote) );
686 m_unConnectionIDLocal = 0;
687 m_unConnectionIDRemote = 0;
688 m_pParentListenSocket = nullptr;
689 m_pPollGroup = nullptr;
690 m_hSelfInParentListenSocketMap = -1;
691 m_bCertHasIdentity = false;
692 m_bCryptKeysValid = false;
693 m_eNegotiatedCipher = k_ESteamNetworkingSocketsCipher_INVALID;
694 memset( m_szAppName, 0, sizeof( m_szAppName ) );
695 memset( m_szDescription, 0, sizeof( m_szDescription ) );
696 m_bConnectionInitiatedRemotely = false;
697 m_pTransport = nullptr;
698 m_nSupressStateChangeCallbacks = 0;
699
700 // Initialize configuration using parent interface for now.
701 m_connectionConfig.Init( &m_pSteamNetworkingSocketsInterface->m_connectionConfig );
702
703 // We should always hold the lock while initializing a connection
704 m_pLock = &m_defaultLock;
705 scopeLock.Lock( *m_pLock );
706 }
707
~CSteamNetworkConnectionBase()708 CSteamNetworkConnectionBase::~CSteamNetworkConnectionBase()
709 {
710 Assert( m_eConnectionState == k_ESteamNetworkingConnectionState_Dead );
711 Assert( m_eConnectionWireState == k_ESteamNetworkingConnectionState_Dead );
712 Assert( m_queueRecvMessages.empty() );
713 Assert( m_pParentListenSocket == nullptr );
714
715 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
716 g_tables_lock.AssertHeldByCurrentThread();
717
718 // Remove from global connection list
719 // FIXME - This doesn't work! We don't hold table lock,
720 // and we cannot take it here without potentially introducing deadlock
721 if ( m_hConnectionSelf != k_HSteamNetConnection_Invalid )
722 {
723 int idx = g_mapConnections.Find( uint16( m_hConnectionSelf ) );
724 if ( idx == g_mapConnections.InvalidIndex() || g_mapConnections[ idx ] != this )
725 {
726 AssertMsg( false, "Connection list bookeeping corruption" );
727 FOR_EACH_HASHMAP( g_mapConnections, i )
728 {
729 if ( g_mapConnections[i] == this )
730 g_mapConnections.RemoveAt( i );
731 }
732 }
733 else
734 {
735 g_mapConnections[ idx ] = nullptr; // Just for grins
736 g_mapConnections.RemoveAt( idx );
737 }
738
739 m_hConnectionSelf = k_HSteamNetConnection_Invalid;
740 }
741
742 // Save connection ID so we avoid using the same thing in the very near future.
743 if ( m_unConnectionIDLocal )
744 {
745 // Trim history to max. If we're really cycling through connections fast, this
746 // history won't be very useful, but that should be an extremely rare edge case,
747 // and the worst thing that happens is that we have a higher chance of reusing
748 // a connection ID that shares the same bottom 16 bits.
749 while ( s_vecRecentLocalConnectionIDs.Count() >= k_nMaxRecentLocalConnectionIDs )
750 s_vecRecentLocalConnectionIDs.Remove( 0 );
751 s_vecRecentLocalConnectionIDs.AddToTail( (uint16)m_unConnectionIDLocal );
752
753 // Clear it, since this function should be idempotent
754 m_unConnectionIDLocal = 0;
755 }
756 }
757
758 static std_vector<CSteamNetworkConnectionBase *> s_vecPendingDeleteConnections;
759 static ShortDurationLock s_lockPendingDeleteConnections( "connection_delete_queue" );
760
ConnectionQueueDestroy()761 void CSteamNetworkConnectionBase::ConnectionQueueDestroy()
762 {
763 AssertLocksHeldByCurrentThread();
764
765 // Make sure all resources have been freed, etc
766 FreeResources();
767
768 // We don't need to be in the thinker list
769 IThinker::ClearNextThinkTime();
770
771 // Put into list
772 s_lockPendingDeleteConnections.lock();
773 s_vecPendingDeleteConnections.push_back(this);
774 s_lockPendingDeleteConnections.unlock();
775 }
776
ProcessDeletionList()777 void CSteamNetworkConnectionBase::ProcessDeletionList()
778 {
779 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
780 if ( s_vecPendingDeleteConnections.empty() )
781 return;
782 TableScopeLock tablesLock( g_tables_lock );
783 s_lockPendingDeleteConnections.lock();
784 for ( CSteamNetworkConnectionBase *pConnection: s_vecPendingDeleteConnections )
785 delete pConnection;
786 s_vecPendingDeleteConnections.clear();
787 s_lockPendingDeleteConnections.unlock();
788 }
789
TransportDestroySelfNow()790 void CConnectionTransport::TransportDestroySelfNow()
791 {
792 AssertLocksHeldByCurrentThread();
793
794 // Call virtual functions while we still can
795 TransportFreeResources();
796
797 // Self destruct NOW
798 delete this;
799 }
800
TransportFreeResources()801 void CConnectionTransport::TransportFreeResources()
802 {
803 }
804
FreeResources()805 void CSteamNetworkConnectionBase::FreeResources()
806 {
807 AssertLocksHeldByCurrentThread();
808
809 // Make sure we're marked in the dead state, and also if we were in an
810 // API-visible state, this will queue the state change notification
811 // while we still know who our listen socket is (if any).
812 //
813 // NOTE: Once this happens, any table lookup that finds us will return NULL.
814 // So we basically don't exist to you if all you have is a handle
815 SetState( k_ESteamNetworkingConnectionState_Dead, SteamNetworkingSockets_GetLocalTimestamp() );
816
817 // Discard any messages that weren't retrieved
818 g_lockAllRecvMessageQueues.lock();
819 m_queueRecvMessages.PurgeMessages();
820 g_lockAllRecvMessageQueues.unlock();
821
822 // If we are in a poll group, remove us from the group
823 RemoveFromPollGroup();
824
825 // Detach from the listen socket that owns us, if any
826 if ( m_pParentListenSocket )
827 m_pParentListenSocket->AboutToDestroyChildConnection( this );
828
829 // Make sure and clean out crypto keys and such now
830 ClearCrypto();
831
832 // Clean up our transport
833 DestroyTransport();
834 }
835
DestroyTransport()836 void CSteamNetworkConnectionBase::DestroyTransport()
837 {
838 AssertLocksHeldByCurrentThread( "DestroyTransport" );
839 if ( m_pTransport )
840 {
841 m_pTransport->TransportDestroySelfNow();
842 m_pTransport = nullptr;
843 }
844 }
845
RemoveFromPollGroup()846 void CSteamNetworkConnectionBase::RemoveFromPollGroup()
847 {
848 AssertLocksHeldByCurrentThread( "RemoveFromPollGroup" );
849 if ( !m_pPollGroup )
850 return;
851 PollGroupScopeLock pollGroupLock( m_pPollGroup->m_lock );
852
853 // Scan all of our messages, and make sure they are not in the secondary queue
854 {
855 ShortDurationScopeLock lockMessageQueues( g_lockAllRecvMessageQueues );
856 for ( CSteamNetworkingMessage *pMsg = m_queueRecvMessages.m_pFirst ; pMsg ; pMsg = pMsg->m_linksSecondaryQueue.m_pNext )
857 {
858 Assert( pMsg->m_links.m_pQueue == &m_queueRecvMessages );
859
860 // It *should* be in the secondary queue of the poll group
861 Assert( pMsg->m_linksSecondaryQueue.m_pQueue == &m_pPollGroup->m_queueRecvMessages );
862
863 // OK, do the work
864 pMsg->UnlinkFromQueue( &CSteamNetworkingMessage::m_linksSecondaryQueue );
865 }
866 }
867
868 // Remove us from the poll group's list. DbgVerify because we should be in the list!
869 DbgVerify( m_pPollGroup->m_vecConnections.FindAndFastRemove( this ) );
870
871 // We're not in a poll group anymore
872 m_pPollGroup = nullptr;
873 }
874
SetPollGroup(CSteamNetworkPollGroup * pPollGroup)875 void CSteamNetworkConnectionBase::SetPollGroup( CSteamNetworkPollGroup *pPollGroup )
876 {
877 AssertLocksHeldByCurrentThread( "SetPollGroup" );
878
879 // Quick early-out for no change
880 if ( m_pPollGroup == pPollGroup )
881 return;
882
883 // Clearing it?
884 if ( !pPollGroup )
885 {
886 RemoveFromPollGroup();
887 return;
888 }
889
890 // Grab locks for old and new poll groups. Remember, we can take multiple locks without
891 // worrying about deadlock because we hold the global lock
892 PollGroupScopeLock pollGroupLockNew( pPollGroup->m_lock );
893 PollGroupScopeLock pollGroupLockOld;
894 if ( m_pPollGroup )
895 pollGroupLockOld.Lock( m_pPollGroup->m_lock );
896
897 // Scan all messages that are already queued for this connection,
898 // and insert them into the poll groups queue in the (approximate)
899 // appropriate spot. Using local timestamps should be really close
900 // for ordering messages between different connections. Remember
901 // that the API very clearly does not provide strong guarantees
902 // regarding ordering of messages from different connections, and
903 // really anybody who is expecting or relying on such guarantees
904 // is probably doing something wrong.
905 {
906 ShortDurationScopeLock lockMessageQueues( g_lockAllRecvMessageQueues );
907 CSteamNetworkingMessage *pInsertBefore = pPollGroup->m_queueRecvMessages.m_pFirst;
908 for ( CSteamNetworkingMessage *pMsg = m_queueRecvMessages.m_pFirst ; pMsg ; pMsg = pMsg->m_links.m_pNext )
909 {
910 Assert( pMsg->m_links.m_pQueue == &m_queueRecvMessages );
911
912 // Unlink it from existing poll group queue, if any
913 if ( pMsg->m_linksSecondaryQueue.m_pQueue )
914 {
915 Assert( m_pPollGroup && pMsg->m_linksSecondaryQueue.m_pQueue == &m_pPollGroup->m_queueRecvMessages );
916 pMsg->UnlinkFromQueue( &CSteamNetworkingMessage::m_linksSecondaryQueue );
917 }
918 else
919 {
920 Assert( !m_pPollGroup );
921 }
922
923 // Scan forward in the poll group message queue, until we find the insertion point
924 for (;;)
925 {
926
927 // End of queue?
928 if ( !pInsertBefore )
929 {
930 pMsg->LinkToQueueTail( &CSteamNetworkingMessage::m_linksSecondaryQueue, &pPollGroup->m_queueRecvMessages );
931 break;
932 }
933
934 Assert( pInsertBefore->m_linksSecondaryQueue.m_pQueue == &pPollGroup->m_queueRecvMessages );
935 if ( pInsertBefore->m_usecTimeReceived > pMsg->m_usecTimeReceived )
936 {
937 pMsg->LinkBefore( pInsertBefore, &CSteamNetworkingMessage::m_linksSecondaryQueue, &pPollGroup->m_queueRecvMessages );
938 break;
939 }
940
941 pInsertBefore = pInsertBefore->m_linksSecondaryQueue.m_pNext;
942 }
943 }
944 }
945
946 // Tell previous poll group, if any, that we are no longer with them
947 if ( m_pPollGroup )
948 {
949 DbgVerify( m_pPollGroup->m_vecConnections.FindAndFastRemove( this ) );
950 }
951
952 // Link to new poll group
953 m_pPollGroup = pPollGroup;
954 Assert( !m_pPollGroup->m_vecConnections.HasElement( this ) );
955 m_pPollGroup->m_vecConnections.AddToTail( this );
956 }
957
BInitConnection(SteamNetworkingMicroseconds usecNow,int nOptions,const SteamNetworkingConfigValue_t * pOptions,SteamDatagramErrMsg & errMsg)958 bool CSteamNetworkConnectionBase::BInitConnection( SteamNetworkingMicroseconds usecNow, int nOptions, const SteamNetworkingConfigValue_t *pOptions, SteamDatagramErrMsg &errMsg )
959 {
960 AssertLocksHeldByCurrentThread( "Base::BInitConnection" );
961
962 // Should only be called while we are in the initial state
963 Assert( GetState() == k_ESteamNetworkingConnectionState_None );
964
965 // Make sure MTU values are initialized
966 UpdateMTUFromConfig();
967
968 Assert( m_hConnectionSelf == k_HSteamNetConnection_Invalid );
969
970 Assert( m_pParentListenSocket == nullptr || m_pSteamNetworkingSocketsInterface == m_pParentListenSocket->m_pSteamNetworkingSocketsInterface );
971
972 // We need to know who we are
973 if ( m_identityLocal.IsInvalid() )
974 {
975 if ( !m_pSteamNetworkingSocketsInterface->GetIdentity( &m_identityLocal ) )
976 {
977 V_strcpy_safe( errMsg, "We don't know our local identity." );
978 return false;
979 }
980 }
981
982 m_eEndReason = k_ESteamNetConnectionEnd_Invalid;
983 m_szEndDebug[0] = '\0';
984 m_statsEndToEnd.Init( usecNow, true ); // Until we go connected don't try to send acks, etc
985 m_usecWhenCreated = usecNow;
986
987 // Select random connection ID, and make sure it passes certain sanity checks
988 {
989 Assert( m_unConnectionIDLocal == 0 );
990
991 // OK, even though *usually* we cannot take the table lock after holding
992 // a connection lock (since we often take them in the opposite order),
993 // in this case it's OK because:
994 //
995 // 1.) We hold the global lock AND
996 // 2.) This is a new connection, and not previously in the table so there is no way
997 // for any other thread that might be holding the table lock at this time to
998 // subsequently try to wait on any locks that we hold.
999 TableScopeLock tableLock( g_tables_lock );
1000
1001 // We make sure the lower 16 bits are unique. Make sure we don't have too many connections.
1002 // This definitely could be relaxed, but honestly we don't expect this library to be used in situations
1003 // where you need that many connections.
1004 if ( g_mapConnections.Count() >= 0x1fff )
1005 {
1006 V_strcpy_safe( errMsg, "Too many connections." );
1007 return false;
1008 }
1009
1010 int tries = 0;
1011 for (;;) {
1012 if ( ++tries > 10000 )
1013 {
1014 V_strcpy_safe( errMsg, "Unable to find unique connection ID" );
1015 return false;
1016 }
1017 CCrypto::GenerateRandomBlock( &m_unConnectionIDLocal, sizeof(m_unConnectionIDLocal) );
1018
1019 // Make sure neither half is zero
1020 if ( ( m_unConnectionIDLocal & 0xffff ) == 0 )
1021 continue;
1022 if ( ( m_unConnectionIDLocal & 0xffff0000 ) == 0 )
1023 continue;
1024
1025 // Check recent connections
1026 if ( s_vecRecentLocalConnectionIDs.HasElement( (uint16)m_unConnectionIDLocal ) )
1027 continue;
1028
1029 // Check active connections
1030 if ( g_mapConnections.HasElement( (uint16)m_unConnectionIDLocal ) )
1031 continue;
1032
1033 // This one's good
1034 break;
1035 }
1036
1037 // Let's use the the connection ID as the connection handle. It's random, not reused
1038 // within a short time interval, and we print it in our debugging in places, and you
1039 // can see it on the wire for debugging. In the past we has a "clever" method of
1040 // assigning the handle that had some cute performance tricks for lookups and
1041 // guaranteeing handles wouldn't be reused. But making it be the same as the
1042 // ConnectionID is probably just more useful and less confusing.
1043 m_hConnectionSelf = m_unConnectionIDLocal;
1044
1045 // Add it to our table of active sockets.
1046 g_mapConnections.Insert( int16( m_hConnectionSelf ), this );
1047 } // Release table scope lock
1048
1049 // Set options, if any
1050 if ( pOptions )
1051 {
1052 for ( int i = 0 ; i < nOptions ; ++i )
1053 {
1054 if ( !m_pSteamNetworkingSocketsInterface->m_pSteamNetworkingUtils->SetConfigValueStruct( pOptions[i], k_ESteamNetworkingConfig_Connection, m_hConnectionSelf ) )
1055 {
1056 V_sprintf_safe( errMsg, "Error setting option %d", pOptions[i].m_eValue );
1057 return false;
1058 }
1059 }
1060 }
1061 else if ( nOptions != 0 )
1062 {
1063 V_strcpy_safe( errMsg, "Options list is NULL, but nOptions != 0?" );
1064 return false;
1065 }
1066
1067 // Bind effective user data into the connection now. It can no longer be inherited
1068 m_connectionConfig.m_ConnectionUserData.Set( m_connectionConfig.m_ConnectionUserData.Get() );
1069
1070 // Make sure a description has been set for debugging purposes
1071 SetDescription();
1072
1073 // Clear everything out
1074 ClearCrypto();
1075
1076 // We should still be in the initial state
1077 Assert( GetState() == k_ESteamNetworkingConnectionState_None );
1078
1079 // Take action to start obtaining a cert, or if we already have one, then set it now
1080 InitConnectionCrypto( usecNow );
1081 if ( GetState() != k_ESteamNetworkingConnectionState_None )
1082 {
1083 Assert( GetState() == k_ESteamNetworkingConnectionState_ProblemDetectedLocally );
1084 V_sprintf_safe( errMsg, "Crypto init error. %s", m_szEndDebug );
1085 return false;
1086 }
1087
1088 return true;
1089 }
1090
BSupportsSymmetricMode()1091 bool CSteamNetworkConnectionBase::BSupportsSymmetricMode()
1092 {
1093 return false;
1094 }
1095
SetAppName(const char * pszName)1096 void CSteamNetworkConnectionBase::SetAppName( const char *pszName )
1097 {
1098 V_strcpy_safe( m_szAppName, pszName ? pszName : "" );
1099
1100 // Re-calculate description
1101 SetDescription();
1102 }
1103
SetDescription()1104 void CSteamNetworkConnectionBase::SetDescription()
1105 {
1106 AssertLocksHeldByCurrentThread(); // Yes, we need the global lock, too, because the description is often accessed while holding the global lock, but not the connection lock
1107
1108 ConnectionTypeDescription_t szTypeDescription;
1109 GetConnectionTypeDescription( szTypeDescription );
1110
1111 if ( m_szAppName[0] )
1112 V_sprintf_safe( m_szDescription, "#%u %s '%s'", m_unConnectionIDLocal, szTypeDescription, m_szAppName );
1113 else
1114 V_sprintf_safe( m_szDescription, "#%u %s", m_unConnectionIDLocal, szTypeDescription );
1115 }
1116
InitConnectionCrypto(SteamNetworkingMicroseconds usecNow)1117 void CSteamNetworkConnectionBase::InitConnectionCrypto( SteamNetworkingMicroseconds usecNow )
1118 {
1119 BThinkCryptoReady( usecNow );
1120 }
1121
ClearCrypto()1122 void CSteamNetworkConnectionBase::ClearCrypto()
1123 {
1124 AssertLocksHeldByCurrentThread();
1125 m_msgCertRemote.Clear();
1126 m_msgCryptRemote.Clear();
1127 m_bCertHasIdentity = false;
1128 m_bRemoteCertHasTrustedCASignature = false;
1129 m_keyPrivate.Wipe();
1130 ClearLocalCrypto();
1131 }
1132
ClearLocalCrypto()1133 void CSteamNetworkConnectionBase::ClearLocalCrypto()
1134 {
1135 AssertLocksHeldByCurrentThread();
1136 m_eNegotiatedCipher = k_ESteamNetworkingSocketsCipher_INVALID;
1137 m_keyExchangePrivateKeyLocal.Wipe();
1138 m_msgCryptLocal.Clear();
1139 m_msgSignedCryptLocal.Clear();
1140 m_bCryptKeysValid = false;
1141 m_cryptContextSend.Wipe();
1142 m_cryptContextRecv.Wipe();
1143 m_cryptIVSend.Wipe();
1144 m_cryptIVRecv.Wipe();
1145 }
1146
RecvNonDataSequencedPacket(int64 nPktNum,SteamNetworkingMicroseconds usecNow)1147 void CSteamNetworkConnectionBase::RecvNonDataSequencedPacket( int64 nPktNum, SteamNetworkingMicroseconds usecNow )
1148 {
1149 // Note: order of operations is important betwen these two calls
1150
1151 // Let SNP know when we received it, so we can track loss events and send acks. We do
1152 // not schedule acks to be sent at this time, but when they are sent, we will implicitly
1153 // ack this one
1154 SNP_RecordReceivedPktNum( nPktNum, usecNow, false );
1155
1156 // Update general sequence number/stats tracker for the end-to-end flow.
1157 m_statsEndToEnd.TrackProcessSequencedPacket( nPktNum, usecNow, 0 );
1158 }
1159
BThinkCryptoReady(SteamNetworkingMicroseconds usecNow)1160 bool CSteamNetworkConnectionBase::BThinkCryptoReady( SteamNetworkingMicroseconds usecNow )
1161 {
1162 // Should only be called from initial states
1163 AssertLocksHeldByCurrentThread();
1164 Assert( GetState() == k_ESteamNetworkingConnectionState_None || GetState() == k_ESteamNetworkingConnectionState_Connecting );
1165
1166 // Do we already have a cert?
1167 if ( m_msgSignedCertLocal.has_cert() )
1168 return true;
1169
1170 // If we are using an anonymous identity, then always use self-signed.
1171 // CA's should never issue a certificate for this identity, because that
1172 // is meaningless. No peer should ever honor such a certificate.
1173 if ( m_identityLocal.IsLocalHost() )
1174 {
1175 SetLocalCertUnsigned();
1176 return true;
1177 }
1178
1179 // Check for fetching a cert, if a previous cert attempt failed,
1180 // or the cert we have is old
1181 #ifdef STEAMNETWORKINGSOCKETS_CAN_REQUEST_CERT
1182 if ( AllowLocalUnsignedCert() != k_EUnsignedCert_Allow )
1183 {
1184
1185 // Make sure request is in flight if needed
1186 // If this fails (either immediately, or asynchronously), we will
1187 // get a CertFailed call with the appropriate code, and we can decide
1188 // what we want to do.
1189 m_pSteamNetworkingSocketsInterface->CheckAuthenticationPrerequisites( usecNow );
1190
1191 // Handle synchronous failure.
1192 if ( GetState() != k_ESteamNetworkingConnectionState_None && GetState() != k_ESteamNetworkingConnectionState_Connecting )
1193 return false;
1194
1195 // If fetching of cert or trusted cert list in flight, then wait for that to finish
1196 SteamNetAuthenticationStatus_t authStatus;
1197 m_pSteamNetworkingSocketsInterface->GetAuthenticationStatus( &authStatus );
1198 switch ( authStatus.m_eAvail )
1199 {
1200 case k_ESteamNetworkingAvailability_CannotTry:
1201 case k_ESteamNetworkingAvailability_Failed:
1202 case k_ESteamNetworkingAvailability_Previously:
1203 case k_ESteamNetworkingAvailability_NeverTried:
1204 default:
1205 AssertMsg2( false, "Unexpected auth avail %d (%s)", authStatus.m_eAvail, authStatus.m_debugMsg );
1206 break;
1207
1208 case k_ESteamNetworkingAvailability_Retrying:
1209 case k_ESteamNetworkingAvailability_Waiting:
1210 case k_ESteamNetworkingAvailability_Attempting:
1211 // Keep waiting
1212 return false;
1213
1214 case k_ESteamNetworkingAvailability_Current:
1215 break;
1216 }
1217
1218 }
1219 #endif
1220
1221 // Already have a signed cert?
1222 int nSecondsUntilCertExpiry = m_pSteamNetworkingSocketsInterface->GetSecondsUntilCertExpiry();
1223 if ( nSecondsUntilCertExpiry > 0 )
1224 {
1225
1226 // We do have a cert -- but if it's close to expiring, wait for any active fetch to finish,
1227 // because there's a good chance that our peer will reject it. (We usually refresh our certs
1228 // well ahead of time, so we really should never hit this.) Note that if this request
1229 // fails, we will get a callback, and an opportunity to attempt to proceed with an unsigned cert
1230 if ( nSecondsUntilCertExpiry < 300 && m_pSteamNetworkingSocketsInterface->BCertRequestInFlight() )
1231 return false;
1232
1233 // Use it!
1234 SpewVerbose( "[%s] Our cert expires in %d seconds.\n", GetDescription(), nSecondsUntilCertExpiry );
1235 SetLocalCert( m_pSteamNetworkingSocketsInterface->m_msgSignedCert, m_pSteamNetworkingSocketsInterface->m_keyPrivateKey, m_pSteamNetworkingSocketsInterface->BCertHasIdentity() );
1236 return true;
1237 }
1238
1239 // Check if we want to intentionally disable auth
1240 if ( AllowLocalUnsignedCert() == k_EUnsignedCert_Allow )
1241 {
1242 SetLocalCertUnsigned();
1243 return true;
1244 }
1245
1246 // Otherwise, we don't have a signed cert (yet?).
1247 #ifndef STEAMNETWORKINGSOCKETS_CAN_REQUEST_CERT
1248 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_InternalError, "Need a cert authority!" );
1249 Assert( false );
1250 #endif
1251 return false;
1252 }
1253
InterfaceGotCert()1254 void CSteamNetworkConnectionBase::InterfaceGotCert()
1255 {
1256 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
1257 ConnectionScopeLock connectionLock( *this );
1258
1259 // Make sure we care about this
1260 if ( GetState() != k_ESteamNetworkingConnectionState_Connecting )
1261 return;
1262 if ( BHasLocalCert() )
1263 return;
1264
1265 // Setup with this cert
1266 SetLocalCert( m_pSteamNetworkingSocketsInterface->m_msgSignedCert, m_pSteamNetworkingSocketsInterface->m_keyPrivateKey, m_pSteamNetworkingSocketsInterface->BCertHasIdentity() );
1267
1268 // Don't check state machine now, let's just schedule immediate wake up to deal with it
1269 SetNextThinkTime( SteamNetworkingSockets_GetLocalTimestamp() );
1270 }
1271
SetLocalCert(const CMsgSteamDatagramCertificateSigned & msgSignedCert,const CECSigningPrivateKey & keyPrivate,bool bCertHasIdentity)1272 void CSteamNetworkConnectionBase::SetLocalCert( const CMsgSteamDatagramCertificateSigned &msgSignedCert, const CECSigningPrivateKey &keyPrivate, bool bCertHasIdentity )
1273 {
1274 AssertLocksHeldByCurrentThread();
1275
1276 Assert( msgSignedCert.has_cert() );
1277 Assert( keyPrivate.IsValid() );
1278
1279 // Ug, we have to save off the private key. I hate to have copies of the private key,
1280 // but we'll only keep this around for a brief time. It's possible for the
1281 // interface to get a new cert (with a new private key) while we are starting this
1282 // connection. We'll keep using the old one, which may be totally valid.
1283 m_keyPrivate.CopyFrom( keyPrivate );
1284
1285 // Save off the signed certificate
1286 m_msgSignedCertLocal = msgSignedCert;
1287 m_bCertHasIdentity = bCertHasIdentity;
1288
1289 // If we are the "client", then we can wrap it up right now
1290 if ( !m_bConnectionInitiatedRemotely )
1291 {
1292 SetCryptoCipherList();
1293 FinalizeLocalCrypto();
1294 }
1295 }
1296
SetCryptoCipherList()1297 void CSteamNetworkConnectionBase::SetCryptoCipherList()
1298 {
1299 AssertLocksHeldByCurrentThread();
1300 Assert( m_msgCryptLocal.ciphers_size() == 0 ); // Should only do this once
1301
1302 // Select the ciphers we want to use, in preference order.
1303 // Also, lock it, we cannot change it any more
1304 m_connectionConfig.m_Unencrypted.Lock();
1305 int unencrypted = m_connectionConfig.m_Unencrypted.Get();
1306 switch ( unencrypted )
1307 {
1308 default:
1309 AssertMsg( false, "Unexpected value for 'Unencrypted' config value" );
1310 // FALLTHROUGH
1311 case 0:
1312 // Not allowed
1313 m_msgCryptLocal.add_ciphers( k_ESteamNetworkingSocketsCipher_AES_256_GCM );
1314 break;
1315
1316 case 1:
1317 // Allowed, but prefer encrypted
1318 m_msgCryptLocal.add_ciphers( k_ESteamNetworkingSocketsCipher_AES_256_GCM );
1319 m_msgCryptLocal.add_ciphers( k_ESteamNetworkingSocketsCipher_NULL );
1320 break;
1321
1322 case 2:
1323 // Allowed, preferred
1324 m_msgCryptLocal.add_ciphers( k_ESteamNetworkingSocketsCipher_NULL );
1325 m_msgCryptLocal.add_ciphers( k_ESteamNetworkingSocketsCipher_AES_256_GCM );
1326 break;
1327
1328 case 3:
1329 // Required
1330 m_msgCryptLocal.add_ciphers( k_ESteamNetworkingSocketsCipher_NULL );
1331 break;
1332 }
1333 }
1334
FinalizeLocalCrypto()1335 void CSteamNetworkConnectionBase::FinalizeLocalCrypto()
1336 {
1337 AssertLocksHeldByCurrentThread( "FinalizeLocalCrypto" );
1338
1339 // Make sure we have what we need
1340 Assert( m_msgCryptLocal.ciphers_size() > 0 );
1341 Assert( m_keyPrivate.IsValid() );
1342
1343 // Should only do this once
1344 Assert( !m_msgSignedCryptLocal.has_info() );
1345
1346 // Set protocol version
1347 m_msgCryptLocal.set_protocol_version( k_nCurrentProtocolVersion );
1348
1349 // Generate a keypair for key exchange
1350 CECKeyExchangePublicKey publicKeyLocal;
1351 CCrypto::GenerateKeyExchangeKeyPair( &publicKeyLocal, &m_keyExchangePrivateKeyLocal );
1352 m_msgCryptLocal.set_key_type( CMsgSteamDatagramSessionCryptInfo_EKeyType_CURVE25519 );
1353 publicKeyLocal.GetRawDataAsStdString( m_msgCryptLocal.mutable_key_data() );
1354
1355 // Generate some more randomness for the secret key
1356 uint64 crypt_nonce;
1357 CCrypto::GenerateRandomBlock( &crypt_nonce, sizeof(crypt_nonce) );
1358 m_msgCryptLocal.set_nonce( crypt_nonce );
1359
1360 // Serialize and sign the crypt key with the private key that matches this cert
1361 m_msgSignedCryptLocal.set_info( m_msgCryptLocal.SerializeAsString() );
1362 CryptoSignature_t sig;
1363 m_keyPrivate.GenerateSignature( m_msgSignedCryptLocal.info().c_str(), m_msgSignedCryptLocal.info().length(), &sig );
1364 m_msgSignedCryptLocal.set_signature( &sig, sizeof(sig) );
1365
1366 // Note: In certain circumstances, we may need to do this again, so don't wipte the key just yet
1367 //m_keyPrivate.Wipe();
1368
1369 // Probably a state change relevant to diagnostics
1370 CheckScheduleDiagnosticsUpdateASAP();
1371 }
1372
SetLocalCertUnsigned()1373 void CSteamNetworkConnectionBase::SetLocalCertUnsigned()
1374 {
1375 AssertLocksHeldByCurrentThread();
1376
1377 // Generate a keypair
1378 CECSigningPrivateKey keyPrivate;
1379 CECSigningPublicKey keyPublic;
1380 CCrypto::GenerateSigningKeyPair( &keyPublic, &keyPrivate );
1381
1382 // Generate a cert
1383 CMsgSteamDatagramCertificate msgCert;
1384 keyPublic.GetRawDataAsStdString( msgCert.mutable_key_data() );
1385 msgCert.set_key_type( CMsgSteamDatagramCertificate_EKeyType_ED25519 );
1386 SteamNetworkingIdentityToProtobuf( m_identityLocal, msgCert, identity_string, legacy_identity_binary, legacy_steam_id );
1387 msgCert.add_app_ids( m_pSteamNetworkingSocketsInterface->m_pSteamNetworkingUtils->GetAppID() );
1388
1389 // Should we set an expiry? I mean it's unsigned, so it has zero value, so probably not
1390 //s_msgCertLocal.set_time_created( );
1391
1392 // Serialize into "signed" message type, although we won't actually sign it.
1393 CMsgSteamDatagramCertificateSigned msgSignedCert;
1394 msgSignedCert.set_cert( msgCert.SerializeAsString() );
1395
1396 // Standard init, as if this were a normal cert
1397 SetLocalCert( msgSignedCert, keyPrivate, true );
1398 }
1399
CertRequestFailed(ESteamNetConnectionEnd nConnectionEndReason,const char * pszMsg)1400 void CSteamNetworkConnectionBase::CertRequestFailed( ESteamNetConnectionEnd nConnectionEndReason, const char *pszMsg )
1401 {
1402 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
1403 ConnectionScopeLock connectionLock( *this );
1404
1405 // Make sure we care about this
1406 if ( GetState() != k_ESteamNetworkingConnectionState_Connecting && GetState() != k_ESteamNetworkingConnectionState_None )
1407 return;
1408 if ( BHasLocalCert() )
1409 return;
1410
1411 // Do we require a signed cert?
1412 EUnsignedCert eLocalUnsignedCert = AllowLocalUnsignedCert();
1413 if ( eLocalUnsignedCert == k_EUnsignedCert_Disallow )
1414 {
1415 // This is fatal
1416 SpewWarning( "[%s] Cannot use unsigned cert; failing connection.\n", GetDescription() );
1417 ConnectionState_ProblemDetectedLocally( nConnectionEndReason, "Cert failure: %s", pszMsg );
1418 return;
1419 }
1420 if ( eLocalUnsignedCert == k_EUnsignedCert_AllowWarn )
1421 SpewWarning( "[%s] Continuing with self-signed cert.\n", GetDescription() );
1422 SetLocalCertUnsigned();
1423
1424 // Schedule immediate wake up to check on state machine
1425 SetNextThinkTime( SteamNetworkingSockets_GetLocalTimestamp() );
1426 }
1427
BRecvCryptoHandshake(const CMsgSteamDatagramCertificateSigned & msgCert,const CMsgSteamDatagramSessionCryptInfoSigned & msgSessionInfo,bool bServer)1428 bool CSteamNetworkConnectionBase::BRecvCryptoHandshake( const CMsgSteamDatagramCertificateSigned &msgCert, const CMsgSteamDatagramSessionCryptInfoSigned &msgSessionInfo, bool bServer )
1429 {
1430 AssertLocksHeldByCurrentThread( "BRecvCryptoHandshake" );
1431 SteamNetworkingErrMsg errMsg;
1432
1433 // Have we already done key exchange?
1434 if ( m_bCryptKeysValid )
1435 {
1436 // FIXME - Probably should check that they aren't changing any keys.
1437 Assert( m_eNegotiatedCipher != k_ESteamNetworkingSocketsCipher_INVALID );
1438 return true;
1439 }
1440 Assert( m_eNegotiatedCipher == k_ESteamNetworkingSocketsCipher_INVALID );
1441
1442 // Make sure we have what we need
1443 if ( !msgCert.has_cert() || !msgSessionInfo.has_info() )
1444 {
1445 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCrypt, "Crypto handshake missing cert or session data" );
1446 return false;
1447 }
1448
1449 // Save off the exact serialized data in the cert and crypt info,
1450 // for key generation material.
1451 m_sCertRemote = msgCert.cert();
1452 m_sCryptRemote = msgSessionInfo.info();
1453
1454 // If they presented a signature, it must be valid
1455 const CertAuthScope *pCACertAuthScope = nullptr;
1456 if ( msgCert.has_ca_signature() )
1457 {
1458
1459 // Check the signature and chain of trust, and expiry, and deserialize the signed cert
1460 time_t timeNow = m_pSteamNetworkingSocketsInterface->m_pSteamNetworkingUtils->GetTimeSecure();
1461 pCACertAuthScope = CertStore_CheckCert( msgCert, m_msgCertRemote, timeNow, errMsg );
1462 if ( !pCACertAuthScope )
1463 {
1464 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCert, "Bad cert: %s", errMsg );
1465 return false;
1466 }
1467 }
1468 else
1469 {
1470
1471 // Deserialize the cert
1472 if ( !m_msgCertRemote.ParseFromString( m_sCertRemote ) )
1473 {
1474 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCrypt, "Cert failed protobuf decode" );
1475 return false;
1476 }
1477
1478 // We'll check if unsigned certs are allowed below, after we know a bit more info
1479 }
1480
1481 // Check identity from cert
1482 SteamNetworkingIdentity identityCert;
1483 int rIdentity = SteamNetworkingIdentityFromCert( identityCert, m_msgCertRemote, errMsg );
1484 if ( rIdentity < 0 )
1485 {
1486 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCert, "Bad cert identity. %s", errMsg );
1487 return false;
1488 }
1489 if ( rIdentity > 0 && !identityCert.IsLocalHost() )
1490 {
1491
1492 // They sent an identity. Then it must match the identity we expect!
1493 if ( !( identityCert == m_identityRemote ) )
1494 {
1495 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCert, "Cert was issued to %s, not %s",
1496 SteamNetworkingIdentityRender( identityCert ).c_str(), SteamNetworkingIdentityRender( m_identityRemote ).c_str() );
1497 return false;
1498 }
1499
1500 // We require certs to be bound to a particular AppID.
1501 if ( m_msgCertRemote.app_ids_size() == 0 )
1502 {
1503 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCert, "Cert must be bound to an AppID." );
1504 return false;
1505 }
1506 }
1507 else if ( !msgCert.has_ca_signature() )
1508 {
1509 // If we're going to allow an unsigned cert (we'll check below),
1510 // then anything goes, so if they omit the identity, that's fine
1511 // with us because they could have forged anything anyway.
1512 }
1513 else
1514 {
1515
1516 // Signed cert, not issued to a particular identity! This is only allowed
1517 // right now when connecting to anonymous gameservers
1518 if ( !m_identityRemote.GetSteamID().BAnonGameServerAccount() )
1519 {
1520 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCert, "Certs with no identity can only by anonymous gameservers, not %s", SteamNetworkingIdentityRender( m_identityRemote ).c_str() );
1521 return false;
1522 }
1523
1524 // And cert must be scoped to a data center, we don't permit blanked certs for anybody with no restrictions at all
1525 if ( m_msgCertRemote.gameserver_datacenter_ids_size() == 0 )
1526 {
1527 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCert, "Cert with no identity must be scoped to PoPID." );
1528 return false;
1529 }
1530 }
1531
1532 // OK, we've parsed everything out, now do any connection-type-specific checks on the cert
1533 ESteamNetConnectionEnd eRemoteCertFailure = CheckRemoteCert( pCACertAuthScope, errMsg );
1534 if ( eRemoteCertFailure )
1535 {
1536 ConnectionState_ProblemDetectedLocally( eRemoteCertFailure, "%s", errMsg );
1537 return false;
1538 }
1539
1540 // Check the signature of the crypt info
1541 if ( !BCheckSignature( m_sCryptRemote, m_msgCertRemote.key_type(), m_msgCertRemote.key_data(), msgSessionInfo.signature(), errMsg ) )
1542 {
1543 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCrypt, "%s", errMsg );
1544 return false;
1545 }
1546
1547 // Remember if we they were authenticated
1548 m_bRemoteCertHasTrustedCASignature = ( pCACertAuthScope != nullptr );
1549
1550 // Deserialize crypt info
1551 if ( !m_msgCryptRemote.ParseFromString( m_sCryptRemote ) )
1552 {
1553 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCrypt, "Crypt info failed protobuf decode" );
1554 return false;
1555 }
1556
1557 // Protocol version
1558 if ( m_msgCryptRemote.protocol_version() < k_nMinRequiredProtocolVersion )
1559 {
1560 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadProtocolVersion, "Peer is running old software and needs to be updated. (V%u, >=V%u is required)",
1561 m_msgCryptRemote.protocol_version(), k_nMinRequiredProtocolVersion );
1562 return false;
1563 }
1564
1565 // Did they already send a protocol version in an earlier message? If so, it needs to match.
1566 if ( m_statsEndToEnd.m_nPeerProtocolVersion != 0 && m_statsEndToEnd.m_nPeerProtocolVersion != m_msgCryptRemote.protocol_version() )
1567 {
1568 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadProtocolVersion, "Claiming protocol V%u now, but earlier was using V%u",m_msgCryptRemote.protocol_version(), m_statsEndToEnd.m_nPeerProtocolVersion );
1569 return false;
1570 }
1571 m_statsEndToEnd.m_nPeerProtocolVersion = m_msgCryptRemote.protocol_version();
1572
1573 // Starting with protocol 10, the connect request/OK packets always implicitly
1574 // have a packet number of 1, and thus the next packet (often the first data packet)
1575 // is assigned a sequence number of 2, at a minimum.
1576 Assert( m_statsEndToEnd.m_nNextSendSequenceNumber >= 1 );
1577 Assert( m_statsEndToEnd.m_nMaxRecvPktNum >= 0 );
1578 if ( m_statsEndToEnd.m_nPeerProtocolVersion >= 10 )
1579 {
1580 if ( m_statsEndToEnd.m_nNextSendSequenceNumber == 1 )
1581 m_statsEndToEnd.m_nNextSendSequenceNumber = 2;
1582 if ( m_statsEndToEnd.m_nMaxRecvPktNum == 0 )
1583 m_statsEndToEnd.InitMaxRecvPktNum( 1 );
1584 }
1585
1586 // Check for legacy client that didn't send a list of ciphers
1587 if ( m_msgCryptRemote.ciphers_size() == 0 )
1588 m_msgCryptRemote.add_ciphers( k_ESteamNetworkingSocketsCipher_AES_256_GCM );
1589
1590 // We need our own cert. If we don't have one by now, then we might try generating one
1591 if ( !m_msgSignedCertLocal.has_cert() )
1592 {
1593
1594 // Double-check that this is allowed
1595 EUnsignedCert eLocalUnsignedCert = AllowLocalUnsignedCert();
1596 if ( eLocalUnsignedCert == k_EUnsignedCert_Disallow )
1597 {
1598 // Derived class / calling code should check for this and handle it better and fail
1599 // earlier with a more specific error message. (Or allow self-signed certs)
1600 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_InternalError, "We don't have cert, and self-signed certs not allowed" );
1601 return false;
1602 }
1603 if ( eLocalUnsignedCert == k_EUnsignedCert_AllowWarn )
1604 SpewWarning( "[%s] Continuing with self-signed cert.\n", GetDescription() );
1605
1606 // Proceed with an unsigned cert
1607 SetLocalCertUnsigned();
1608 }
1609
1610 // If we are the client, then we have everything we need and can finish up right now
1611 if ( !m_bConnectionInitiatedRemotely )
1612 {
1613 // The server MUST send back the single cipher that they decided to use
1614 if ( m_msgCryptRemote.ciphers_size() != 1 )
1615 {
1616 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCrypt, "Server must select exactly only one cipher!" );
1617 return false;
1618 }
1619 if ( !BFinishCryptoHandshake( bServer ) )
1620 {
1621 Assert( GetState() == k_ESteamNetworkingConnectionState_ProblemDetectedLocally );
1622 return false;
1623 }
1624 }
1625
1626 return true;
1627 }
1628
BFinishCryptoHandshake(bool bServer)1629 bool CSteamNetworkConnectionBase::BFinishCryptoHandshake( bool bServer )
1630 {
1631 AssertLocksHeldByCurrentThread( "BFinishCryptoHandshake" );
1632
1633 // On the server, we have been waiting to decide what ciphers we are willing to use.
1634 // (Because we want to give the app to set any connection options).
1635 if ( m_bConnectionInitiatedRemotely )
1636 {
1637 Assert( m_msgCryptLocal.ciphers_size() == 0 );
1638 SetCryptoCipherList();
1639 }
1640 Assert( m_msgCryptLocal.ciphers_size() > 0 );
1641
1642 // Find a mutually-acceptable cipher
1643 Assert( m_eNegotiatedCipher == k_ESteamNetworkingSocketsCipher_INVALID );
1644 m_eNegotiatedCipher = k_ESteamNetworkingSocketsCipher_INVALID;
1645 for ( int eCipher : m_msgCryptLocal.ciphers() )
1646 {
1647 if ( std::find( m_msgCryptRemote.ciphers().begin(), m_msgCryptRemote.ciphers().end(), eCipher ) != m_msgCryptRemote.ciphers().end() )
1648 {
1649 m_eNegotiatedCipher = ESteamNetworkingSocketsCipher(eCipher);
1650 break;
1651 }
1652 }
1653 if ( m_eNegotiatedCipher == k_ESteamNetworkingSocketsCipher_INVALID )
1654 {
1655 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCrypt, "Failed to negotiate mutually-agreeable cipher" );
1656 return false;
1657 }
1658
1659 // If we're the server, then lock in that single cipher as the only
1660 // acceptable cipher, and then we are ready to seal up our crypt info
1661 // and send it back to them in accept message(s)
1662 if ( m_bConnectionInitiatedRemotely )
1663 {
1664 Assert( !m_msgSignedCryptLocal.has_info() );
1665 m_msgCryptLocal.clear_ciphers();
1666 m_msgCryptLocal.add_ciphers( m_eNegotiatedCipher );
1667 FinalizeLocalCrypto();
1668 }
1669 Assert( m_msgSignedCryptLocal.has_info() );
1670
1671 // At this point, we know that we will never the private key again. So let's
1672 // wipe it now, to minimize the number of copies of this hanging around in memory.
1673 m_keyPrivate.Wipe();
1674
1675 // Key exchange public key
1676 CECKeyExchangePublicKey keyExchangePublicKeyRemote;
1677 if ( m_msgCryptRemote.key_type() != CMsgSteamDatagramSessionCryptInfo_EKeyType_CURVE25519 )
1678 {
1679 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCrypt, "Unsupported DH key type" );
1680 return false;
1681 }
1682 if ( !keyExchangePublicKeyRemote.SetRawDataWithoutWipingInput( m_msgCryptRemote.key_data().c_str(), m_msgCryptRemote.key_data().length() ) )
1683 {
1684 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCrypt, "Invalid DH key" );
1685 return false;
1686 }
1687
1688 // Diffie�Hellman key exchange to get "premaster secret"
1689 AutoWipeFixedSizeBuffer<sizeof(SHA256Digest_t)> premasterSecret;
1690 if ( !CCrypto::PerformKeyExchange( m_keyExchangePrivateKeyLocal, keyExchangePublicKeyRemote, &premasterSecret.m_buf ) )
1691 {
1692 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCrypt, "Key exchange failed" );
1693 return false;
1694 }
1695 //SpewMsg( "%s premaster: %02x%02x%02x%02x\n", bServer ? "Server" : "Client", premasterSecret.m_buf[0], premasterSecret.m_buf[1], premasterSecret.m_buf[2], premasterSecret.m_buf[3] );
1696
1697 // We won't need this again, so go ahead and discard it now.
1698 m_keyExchangePrivateKeyLocal.Wipe();
1699
1700 //
1701 // HMAC Key derivation function.
1702 //
1703 // https://tools.ietf.org/html/rfc5869
1704 // https://docs.google.com/document/d/1g5nIXAIkN_Y-7XJW5K45IblHd_L2f5LTaDUDwvZ5L6g/edit - Google QUIC as of 4/26/2017
1705 //
1706
1707 //
1708 // 1. Extract: take premaster secret from key exchange and mix it so that it's evenly distributed, producing Pseudorandom key ("PRK")
1709 //
1710 uint64 salt[2] = { LittleQWord( m_msgCryptRemote.nonce() ), LittleQWord( m_msgCryptLocal.nonce() ) };
1711 if ( bServer )
1712 std::swap( salt[0], salt[1] );
1713 AutoWipeFixedSizeBuffer<sizeof(SHA256Digest_t)> prk;
1714 CCrypto::GenerateHMAC256( (const uint8 *)salt, sizeof(salt), premasterSecret.m_buf, premasterSecret.k_nSize, &prk.m_buf );
1715 premasterSecret.Wipe();
1716
1717 //
1718 // 2. Expand: Use PRK as seed to generate all the different keys we need, mixing with connection-specific context
1719 //
1720
1721 AutoWipeFixedSizeBuffer<32> cryptKeySend;
1722 AutoWipeFixedSizeBuffer<32> cryptKeyRecv;
1723 COMPILE_TIME_ASSERT( sizeof( cryptKeyRecv ) == sizeof(SHA256Digest_t) );
1724 COMPILE_TIME_ASSERT( sizeof( cryptKeySend ) == sizeof(SHA256Digest_t) );
1725 COMPILE_TIME_ASSERT( sizeof( m_cryptIVRecv ) <= sizeof(SHA256Digest_t) );
1726 COMPILE_TIME_ASSERT( sizeof( m_cryptIVSend ) <= sizeof(SHA256Digest_t) );
1727
1728 uint8 *expandOrder[4] = { cryptKeySend.m_buf, cryptKeyRecv.m_buf, m_cryptIVSend.m_buf, m_cryptIVRecv.m_buf };
1729 int expandSize[4] = { cryptKeySend.k_nSize, cryptKeyRecv.k_nSize, m_cryptIVSend.k_nSize, m_cryptIVRecv.k_nSize };
1730 const std::string *context[4] = { &m_sCertRemote, &m_msgSignedCertLocal.cert(), &m_sCryptRemote, &m_msgSignedCryptLocal.info() };
1731 uint32 unConnectionIDContext[2] = { LittleDWord( m_unConnectionIDLocal ), LittleDWord( m_unConnectionIDRemote ) };
1732
1733 // Make sure that both peers do things the same, so swap "local" and "remote" on one side arbitrarily.
1734 if ( bServer )
1735 {
1736 std::swap( expandOrder[0], expandOrder[1] );
1737 std::swap( expandOrder[2], expandOrder[3] );
1738 std::swap( expandSize[0], expandSize[1] ); // Actually NOP, but makes me feel better
1739 std::swap( expandSize[2], expandSize[3] );
1740 std::swap( context[0], context[1] );
1741 std::swap( context[2], context[3] );
1742 std::swap( unConnectionIDContext[0], unConnectionIDContext[1] );
1743 }
1744 //SpewMsg( "%s unConnectionIDContext = [ %u, %u ]\n", bServer ? "Server" : "Client", unConnectionIDContext[0], unConnectionIDContext[1] );
1745
1746 // Generate connection "context" buffer
1747 CUtlBuffer bufContext( 0, (int)( sizeof(SHA256Digest_t) + sizeof(unConnectionIDContext) + 64 + context[0]->length() + context[1]->length() + context[2]->length() + context[3]->length() ), 0 );
1748 bufContext.SeekPut( CUtlBuffer::SEEK_HEAD, sizeof(SHA256Digest_t) );
1749 uint8 *pStart = (uint8 *)bufContext.PeekPut();
1750
1751 // Write connection ID(s) into context buffer
1752 bufContext.Put( unConnectionIDContext, sizeof(unConnectionIDContext) );
1753
1754 bufContext.Put( "Steam datagram", 14 );
1755 for ( const std::string *c: context )
1756 bufContext.Put( c->c_str(), (int)c->length() );
1757
1758 // Now extract the keys according to the method in the RFC
1759 uint8 *pLastByte = (uint8 *)bufContext.PeekPut();
1760 SHA256Digest_t expandTemp;
1761 for ( int idxExpand = 0 ; idxExpand < 4 ; ++idxExpand )
1762 {
1763 *pLastByte = idxExpand+1;
1764 CCrypto::GenerateHMAC256( pStart, pLastByte - pStart + 1, prk.m_buf, prk.k_nSize, &expandTemp );
1765 V_memcpy( expandOrder[ idxExpand ], &expandTemp, expandSize[ idxExpand ] );
1766
1767 //SpewMsg( "%s key %d: %02x%02x%02x%02x\n", bServer ? "Server" : "Client", idxExpand, expandTemp[0], expandTemp[1], expandTemp[2], expandTemp[3] );
1768
1769 // Copy previous digest to use in generating the next one
1770 pStart = (uint8 *)bufContext.Base();
1771 V_memcpy( pStart, &expandTemp, sizeof(SHA256Digest_t) );
1772 }
1773
1774 // Set encryption keys into the contexts, and set parameters
1775 if (
1776 !m_cryptContextSend.Init( cryptKeySend.m_buf, cryptKeySend.k_nSize, m_cryptIVSend.k_nSize, k_cbSteamNetwokingSocketsEncrytionTagSize )
1777 || !m_cryptContextRecv.Init( cryptKeyRecv.m_buf, cryptKeyRecv.k_nSize, m_cryptIVRecv.k_nSize, k_cbSteamNetwokingSocketsEncrytionTagSize ) )
1778 {
1779 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCrypt, "Error initializing crypto" );
1780 return false;
1781 }
1782
1783 //
1784 // Tidy up key droppings
1785 //
1786 SecureZeroMemory( bufContext.Base(), bufContext.SizeAllocated() );
1787 SecureZeroMemory( expandTemp, sizeof(expandTemp) );
1788
1789 // This isn't sensitive info, but we don't need it any more, so go ahead and free up memory
1790 m_sCertRemote.clear();
1791 m_sCryptRemote.clear();
1792
1793 // Make sure the connection description is set.
1794 // This is often called after we know who the remote host is
1795 SetDescription();
1796
1797 // We're ready
1798 m_bCryptKeysValid = true;
1799 return true;
1800 }
1801
AllowLocalUnsignedCert()1802 EUnsignedCert CSteamNetworkConnectionBase::AllowLocalUnsignedCert()
1803 {
1804 #ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE
1805 // We don't have a cert authority. We probably ought to make this customizable
1806 return k_EUnsignedCert_Allow;
1807 #else
1808 return k_EUnsignedCert_Disallow;
1809 #endif
1810 }
1811
AllowRemoteUnsignedCert()1812 EUnsignedCert CSteamNetworkConnectionBase::AllowRemoteUnsignedCert()
1813 {
1814 #ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE
1815 // We don't have a cert authority. We probably ought to make this customizable
1816 return k_EUnsignedCert_Allow;
1817 #else
1818 return k_EUnsignedCert_Disallow;
1819 #endif
1820 }
1821
CheckRemoteCert(const CertAuthScope * pCACertAuthScope,SteamNetworkingErrMsg & errMsg)1822 ESteamNetConnectionEnd CSteamNetworkConnectionBase::CheckRemoteCert( const CertAuthScope *pCACertAuthScope, SteamNetworkingErrMsg &errMsg )
1823 {
1824 AssertLocksHeldByCurrentThread( "BFinishCryptoHandshake" );
1825
1826 // Allowed for this app?
1827 if ( !CheckCertAppID( m_msgCertRemote, pCACertAuthScope, m_pSteamNetworkingSocketsInterface->m_pSteamNetworkingUtils->GetAppID(), errMsg ) )
1828 return k_ESteamNetConnectionEnd_Remote_BadCert;
1829
1830 // Check if we don't allow unsigned certs
1831 if ( pCACertAuthScope == nullptr )
1832 {
1833 EUnsignedCert eAllow = AllowRemoteUnsignedCert();
1834 if ( eAllow == k_EUnsignedCert_AllowWarn )
1835 {
1836 SpewMsg( "[%s] Remote host is using an unsigned cert. Allowing connection, but it's not secure!\n", GetDescription() );
1837 }
1838 else if ( eAllow != k_EUnsignedCert_Allow )
1839 {
1840 V_strcpy_safe( errMsg, "Unsigned certs are not allowed" );
1841 return k_ESteamNetConnectionEnd_Remote_BadCert;
1842 }
1843 }
1844
1845 return k_ESteamNetConnectionEnd_Invalid;
1846 }
1847
SetUserData(int64 nUserData)1848 void CSteamNetworkConnectionBase::SetUserData( int64 nUserData )
1849 {
1850 m_pLock->AssertHeldByCurrentThread();
1851 m_connectionConfig.m_ConnectionUserData.Set( nUserData );
1852
1853 // Change user data on all messages that haven't been pulled out
1854 // of the queue yet. This way we don't expose the client to weird
1855 // race conditions where they create a connection, and before they
1856 // are able to install their user data, some messages come in
1857 g_lockAllRecvMessageQueues.lock();
1858 for ( CSteamNetworkingMessage *m = m_queueRecvMessages.m_pFirst ; m ; m = m->m_links.m_pNext )
1859 {
1860 Assert( m->m_conn == m_hConnectionSelf );
1861 m->m_nConnUserData = nUserData;
1862 }
1863 g_lockAllRecvMessageQueues.unlock();
1864 }
1865
TransportConnectionStateChanged(ESteamNetworkingConnectionState eOldState)1866 void CConnectionTransport::TransportConnectionStateChanged( ESteamNetworkingConnectionState eOldState )
1867 {
1868 AssertLocksHeldByCurrentThread();
1869 }
1870
BCanSendEndToEndConnectRequest() const1871 bool CConnectionTransport::BCanSendEndToEndConnectRequest() const
1872 {
1873 // You should override this, or your connection should not call it!
1874 Assert( false );
1875 return false;
1876 }
1877
SendEndToEndConnectRequest(SteamNetworkingMicroseconds usecNow)1878 void CConnectionTransport::SendEndToEndConnectRequest( SteamNetworkingMicroseconds usecNow )
1879 {
1880 // You should override this, or your connection should not call it!
1881 Assert( false );
1882 }
1883
TransportPopulateConnectionInfo(SteamNetConnectionInfo_t & info) const1884 void CConnectionTransport::TransportPopulateConnectionInfo( SteamNetConnectionInfo_t &info ) const
1885 {
1886 }
1887
GetDetailedConnectionStatus(SteamNetworkingDetailedConnectionStatus & stats,SteamNetworkingMicroseconds usecNow)1888 void CConnectionTransport::GetDetailedConnectionStatus( SteamNetworkingDetailedConnectionStatus &stats, SteamNetworkingMicroseconds usecNow )
1889 {
1890 }
1891
1892 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_DIAGNOSTICSUI
TransportPopulateDiagnostics(CGameNetworkingUI_ConnectionState & msgConnectionState,SteamNetworkingMicroseconds usecNow)1893 void CConnectionTransport::TransportPopulateDiagnostics( CGameNetworkingUI_ConnectionState &msgConnectionState, SteamNetworkingMicroseconds usecNow )
1894 {
1895 }
1896 #endif
1897
ConnectionPopulateInfo(SteamNetConnectionInfo_t & info) const1898 void CSteamNetworkConnectionBase::ConnectionPopulateInfo( SteamNetConnectionInfo_t &info ) const
1899 {
1900 m_pLock->AssertHeldByCurrentThread();
1901 memset( &info, 0, sizeof(info) );
1902
1903 info.m_eState = CollapseConnectionStateToAPIState( m_eConnectionState );
1904 info.m_hListenSocket = m_pParentListenSocket ? m_pParentListenSocket->m_hListenSocketSelf : k_HSteamListenSocket_Invalid;
1905 info.m_identityRemote = m_identityRemote;
1906 info.m_nUserData = GetUserData();
1907 info.m_eEndReason = m_eEndReason;
1908 V_strcpy_safe( info.m_szEndDebug, m_szEndDebug );
1909 V_strcpy_safe( info.m_szConnectionDescription, m_szDescription );
1910
1911 // Set security flags
1912 if ( !m_bRemoteCertHasTrustedCASignature || m_identityRemote.IsInvalid() || m_identityRemote.m_eType == k_ESteamNetworkingIdentityType_IPAddress )
1913 info.m_nFlags |= k_nSteamNetworkConnectionInfoFlags_Unauthenticated;
1914 if ( m_eNegotiatedCipher <= k_ESteamNetworkingSocketsCipher_NULL )
1915 info.m_nFlags |= k_nSteamNetworkConnectionInfoFlags_Unencrypted;
1916
1917 if ( m_pTransport )
1918 m_pTransport->TransportPopulateConnectionInfo( info );
1919 }
1920
APIGetQuickConnectionStatus(SteamNetworkingQuickConnectionStatus & stats)1921 void CSteamNetworkConnectionBase::APIGetQuickConnectionStatus( SteamNetworkingQuickConnectionStatus &stats )
1922 {
1923 m_pLock->AssertHeldByCurrentThread();
1924 SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
1925
1926 stats.m_eState = CollapseConnectionStateToAPIState( m_eConnectionState );
1927 stats.m_nPing = m_statsEndToEnd.m_ping.m_nSmoothedPing;
1928 if ( m_statsEndToEnd.m_flInPacketsDroppedPct >= 0.0f )
1929 {
1930 Assert( m_statsEndToEnd.m_flInPacketsWeirdSequencePct >= 0.0f );
1931 stats.m_flConnectionQualityLocal = 1.0f - m_statsEndToEnd.m_flInPacketsDroppedPct - m_statsEndToEnd.m_flInPacketsWeirdSequencePct;
1932 Assert( stats.m_flConnectionQualityLocal >= 0.0f );
1933 }
1934 else
1935 {
1936 stats.m_flConnectionQualityLocal = -1.0f;
1937 }
1938
1939 // FIXME - Can SNP give us a more up-to-date value from the feedback packet?
1940 if ( m_statsEndToEnd.m_latestRemote.m_flPacketsDroppedPct >= 0.0f )
1941 {
1942 Assert( m_statsEndToEnd.m_latestRemote.m_flPacketsWeirdSequenceNumberPct >= 0.0f );
1943 stats.m_flConnectionQualityRemote = 1.0f - m_statsEndToEnd.m_latestRemote.m_flPacketsDroppedPct - m_statsEndToEnd.m_latestRemote.m_flPacketsWeirdSequenceNumberPct;
1944 Assert( stats.m_flConnectionQualityRemote >= 0.0f );
1945 }
1946 else
1947 {
1948 stats.m_flConnectionQualityRemote = -1.0f;
1949 }
1950
1951 // Actual current data rates
1952 stats.m_flOutPacketsPerSec = m_statsEndToEnd.m_sent.m_packets.m_flRate;
1953 stats.m_flOutBytesPerSec = m_statsEndToEnd.m_sent.m_bytes.m_flRate;
1954 stats.m_flInPacketsPerSec = m_statsEndToEnd.m_recv.m_packets.m_flRate;
1955 stats.m_flInBytesPerSec = m_statsEndToEnd.m_recv.m_bytes.m_flRate;
1956 SNP_PopulateQuickStats( stats, usecNow );
1957 }
1958
APIGetDetailedConnectionStatus(SteamNetworkingDetailedConnectionStatus & stats,SteamNetworkingMicroseconds usecNow)1959 void CSteamNetworkConnectionBase::APIGetDetailedConnectionStatus( SteamNetworkingDetailedConnectionStatus &stats, SteamNetworkingMicroseconds usecNow )
1960 {
1961 // Connection must be locked, but we don't require the global lock here!
1962 m_pLock->AssertHeldByCurrentThread();
1963
1964 stats.Clear();
1965 ConnectionPopulateInfo( stats.m_info );
1966
1967 // Copy end-to-end stats
1968 m_statsEndToEnd.GetLinkStats( stats.m_statsEndToEnd, usecNow );
1969
1970 // Congestion control and bandwidth estimation
1971 SNP_PopulateDetailedStats( stats.m_statsEndToEnd );
1972
1973 if ( m_pTransport )
1974 m_pTransport->GetDetailedConnectionStatus( stats, usecNow );
1975 }
1976
APISendMessageToConnection(const void * pData,uint32 cbData,int nSendFlags,int64 * pOutMessageNumber)1977 EResult CSteamNetworkConnectionBase::APISendMessageToConnection( const void *pData, uint32 cbData, int nSendFlags, int64 *pOutMessageNumber )
1978 {
1979 // Connection must be locked, but we don't require the global lock here!
1980 m_pLock->AssertHeldByCurrentThread();
1981
1982 if ( pOutMessageNumber )
1983 *pOutMessageNumber = -1;
1984
1985 // Check connection state
1986 switch ( GetState() )
1987 {
1988 case k_ESteamNetworkingConnectionState_None:
1989 case k_ESteamNetworkingConnectionState_FinWait:
1990 case k_ESteamNetworkingConnectionState_Linger:
1991 case k_ESteamNetworkingConnectionState_Dead:
1992 default:
1993 AssertMsg( false, "Why are making API calls on this connection?" );
1994 return k_EResultInvalidState;
1995
1996 case k_ESteamNetworkingConnectionState_Connecting:
1997 case k_ESteamNetworkingConnectionState_FindingRoute:
1998 if ( nSendFlags & k_nSteamNetworkingSend_NoDelay )
1999 return k_EResultIgnored;
2000 break;
2001
2002 case k_ESteamNetworkingConnectionState_Connected:
2003 break;
2004
2005 case k_ESteamNetworkingConnectionState_ClosedByPeer:
2006 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
2007 return k_EResultNoConnection;
2008 }
2009
2010 // Fill out a message object
2011 CSteamNetworkingMessage *pMsg = CSteamNetworkingMessage::New( cbData );
2012 if ( !pMsg )
2013 return k_EResultFail;
2014 pMsg->m_nFlags = nSendFlags;
2015
2016 // Copy in the payload
2017 memcpy( pMsg->m_pData, pData, cbData );
2018
2019 SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
2020
2021 // Connection-type specific logic
2022 int64 nMsgNumberOrResult = _APISendMessageToConnection( pMsg, usecNow, nullptr );
2023 if ( nMsgNumberOrResult > 0 )
2024 {
2025 if ( pOutMessageNumber )
2026 *pOutMessageNumber = nMsgNumberOrResult;
2027 return k_EResultOK;
2028 }
2029 return EResult( -nMsgNumberOrResult );
2030 }
2031
APISendMessageToConnection(CSteamNetworkingMessage * pMsg,SteamNetworkingMicroseconds usecNow,bool * pbThinkImmediately)2032 int64 CSteamNetworkConnectionBase::APISendMessageToConnection( CSteamNetworkingMessage *pMsg, SteamNetworkingMicroseconds usecNow, bool *pbThinkImmediately )
2033 {
2034 m_pLock->AssertHeldByCurrentThread();
2035
2036 // Check connection state
2037 switch ( GetState() )
2038 {
2039 case k_ESteamNetworkingConnectionState_None:
2040 case k_ESteamNetworkingConnectionState_FinWait:
2041 case k_ESteamNetworkingConnectionState_Linger:
2042 case k_ESteamNetworkingConnectionState_Dead:
2043 default:
2044 AssertMsg( false, "Why are making API calls on this connection?" );
2045 pMsg->Release();
2046 return -k_EResultInvalidState;
2047
2048 case k_ESteamNetworkingConnectionState_Connecting:
2049 case k_ESteamNetworkingConnectionState_FindingRoute:
2050 if ( pMsg->m_nFlags & k_nSteamNetworkingSend_NoDelay )
2051 {
2052 pMsg->Release();
2053 return -k_EResultIgnored;
2054 }
2055 break;
2056
2057 case k_ESteamNetworkingConnectionState_Connected:
2058 break;
2059
2060 case k_ESteamNetworkingConnectionState_ClosedByPeer:
2061 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
2062 pMsg->Release();
2063 return -k_EResultNoConnection;
2064 }
2065
2066 return _APISendMessageToConnection( pMsg, usecNow, pbThinkImmediately );
2067 }
2068
_APISendMessageToConnection(CSteamNetworkingMessage * pMsg,SteamNetworkingMicroseconds usecNow,bool * pbThinkImmediately)2069 int64 CSteamNetworkConnectionBase::_APISendMessageToConnection( CSteamNetworkingMessage *pMsg, SteamNetworkingMicroseconds usecNow, bool *pbThinkImmediately )
2070 {
2071
2072 // Message too big?
2073 if ( pMsg->m_cbSize > k_cbMaxSteamNetworkingSocketsMessageSizeSend )
2074 {
2075 AssertMsg2( false, "Message size %d is too big. Max is %d", pMsg->m_cbSize, k_cbMaxSteamNetworkingSocketsMessageSizeSend );
2076 pMsg->Release();
2077 return -k_EResultInvalidParam;
2078 }
2079
2080 // Pass to reliability layer.
2081 return SNP_SendMessage( pMsg, usecNow, pbThinkImmediately );
2082 }
2083
2084
APIFlushMessageOnConnection()2085 EResult CSteamNetworkConnectionBase::APIFlushMessageOnConnection()
2086 {
2087 m_pLock->AssertHeldByCurrentThread();
2088
2089 // Check connection state
2090 switch ( GetState() )
2091 {
2092 case k_ESteamNetworkingConnectionState_None:
2093 case k_ESteamNetworkingConnectionState_FinWait:
2094 case k_ESteamNetworkingConnectionState_Linger:
2095 case k_ESteamNetworkingConnectionState_Dead:
2096 default:
2097 AssertMsg( false, "Why are making API calls on this connection?" );
2098 return k_EResultInvalidState;
2099
2100 case k_ESteamNetworkingConnectionState_Connecting:
2101 case k_ESteamNetworkingConnectionState_FindingRoute:
2102 case k_ESteamNetworkingConnectionState_Connected:
2103 break;
2104
2105 case k_ESteamNetworkingConnectionState_ClosedByPeer:
2106 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
2107 return k_EResultNoConnection;
2108 }
2109
2110 SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
2111 return SNP_FlushMessage( usecNow );
2112 }
2113
APIReceiveMessages(SteamNetworkingMessage_t ** ppOutMessages,int nMaxMessages)2114 int CSteamNetworkConnectionBase::APIReceiveMessages( SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages )
2115 {
2116 // Connection must be locked, but we don't require the global lock here!
2117 m_pLock->AssertHeldByCurrentThread();
2118
2119 g_lockAllRecvMessageQueues.lock();
2120 int result = m_queueRecvMessages.RemoveMessages( ppOutMessages, nMaxMessages );
2121 g_lockAllRecvMessageQueues.unlock();
2122
2123 return result;
2124 }
2125
DecryptDataChunk(uint16 nWireSeqNum,int cbPacketSize,const void * pChunk,int cbChunk,RecvPacketContext_t & ctx)2126 bool CSteamNetworkConnectionBase::DecryptDataChunk( uint16 nWireSeqNum, int cbPacketSize, const void *pChunk, int cbChunk, RecvPacketContext_t &ctx )
2127 {
2128 AssertLocksHeldByCurrentThread();
2129
2130 if ( !m_bCryptKeysValid || !BStateIsActive() )
2131 {
2132 Assert( m_bCryptKeysValid );
2133 Assert( BStateIsActive() );
2134 return false;
2135 }
2136
2137 // Sequence number should be initialized at this point! We had some cases where
2138 // the protocol was not properly assigning a sequence number, but those should be
2139 // fixed now
2140 AssertMsg1( m_statsEndToEnd.m_nMaxRecvPktNum > 0 || m_statsEndToEnd.m_nPeerProtocolVersion < 10, "[%s] packet number not properly initialized!", GetDescription() );
2141
2142 // Get the full end-to-end packet number, check if we should process it
2143 ctx.m_nPktNum = m_statsEndToEnd.ExpandWirePacketNumberAndCheck( nWireSeqNum );
2144 if ( ctx.m_nPktNum <= 0 )
2145 {
2146
2147 // Update raw packet counters numbers, but do not update any logical state such as reply timeouts, etc
2148 m_statsEndToEnd.m_recv.ProcessPacket( cbPacketSize );
2149 return false;
2150 }
2151
2152 // What cipher are we using?
2153 switch ( m_eNegotiatedCipher )
2154 {
2155 default:
2156 AssertMsg1( false, "Bogus cipher %d", m_eNegotiatedCipher );
2157 return false;
2158
2159 case k_ESteamNetworkingSocketsCipher_NULL:
2160 {
2161
2162 // No encryption!
2163 ctx.m_cbPlainText = cbChunk;
2164 ctx.m_pPlainText = pChunk;
2165 }
2166 break;
2167
2168 case k_ESteamNetworkingSocketsCipher_AES_256_GCM:
2169 {
2170
2171 // Adjust the IV by the packet number
2172 *(uint64 *)&m_cryptIVRecv.m_buf += LittleQWord( ctx.m_nPktNum );
2173 //SpewMsg( "Recv decrypt IV %llu + %02x%02x%02x%02x encrypted %d %02x%02x%02x%02x\n",
2174 // *(uint64 *)&m_cryptIVRecv.m_buf,
2175 // m_cryptIVRecv.m_buf[8], m_cryptIVRecv.m_buf[9], m_cryptIVRecv.m_buf[10], m_cryptIVRecv.m_buf[11],
2176 // cbChunk,
2177 // *((byte*)pChunk + 0), *((byte*)pChunk + 1), *((byte*)pChunk + 2), *((byte*)pChunk + 3)
2178 //);
2179
2180 // Decrypt the chunk and check the auth tag
2181 uint32 cbDecrypted = sizeof(ctx.m_decrypted);
2182 bool bDecryptOK = m_cryptContextRecv.Decrypt(
2183 pChunk, cbChunk, // encrypted
2184 m_cryptIVRecv.m_buf, // IV
2185 ctx.m_decrypted, &cbDecrypted, // output
2186 nullptr, 0 // no AAD
2187 );
2188
2189 // Restore the IV to the base value
2190 *(uint64 *)&m_cryptIVRecv.m_buf -= LittleQWord( ctx.m_nPktNum );
2191
2192 // Did decryption fail?
2193 if ( !bDecryptOK ) {
2194
2195 // Just drop packet.
2196 // The assumption is that we either have a bug or some weird thing,
2197 // or that somebody is spoofing / tampering. If it's the latter
2198 // we don't want to magnify the impact of their efforts
2199 SpewWarningRateLimited( ctx.m_usecNow, "[%s] Packet %lld (0x%x) decrypt failed (tampering/spoofing/bug)!",
2200 GetDescription(), (long long)ctx.m_nPktNum, (unsigned)nWireSeqNum );
2201
2202 // Update raw packet counters numbers, but do not update any logical state such as reply timeouts, etc
2203 m_statsEndToEnd.m_recv.ProcessPacket( cbPacketSize );
2204 return false;
2205 }
2206
2207 ctx.m_cbPlainText = (int)cbDecrypted;
2208 ctx.m_pPlainText = ctx.m_decrypted;
2209
2210 //SpewVerbose( "Connection %u recv seqnum %lld (gap=%d) sz=%d %02x %02x %02x %02x\n", m_unConnectionID, unFullSequenceNumber, nGap, cbDecrypted, arDecryptedChunk[0], arDecryptedChunk[1], arDecryptedChunk[2], arDecryptedChunk[3] );
2211 }
2212 break;
2213 }
2214
2215 // OK, we have high confidence that this packet is actually from our peer and has not
2216 // been tampered with. Check the gap. If it's too big, that means we are risking losing
2217 // our ability to keep the sequence numbers in sync on each end. This is a relatively
2218 // large number of outstanding packets. We should never have this many packets
2219 // outstanding unacknowledged. When we stop getting acks we should reduce our packet rate.
2220 // This isn't really a practical limitation, but it is a theoretical limitation if the
2221 // bandwidth is extremely high relatively to the latency.
2222 //
2223 // Even if the packets are on average only half full (~600 bytes), 16k packets is
2224 // around 9MB of data. We probably don't want to have this amount of un-acked data
2225 // in our buffers, anyway. If the packets are tiny it would be less, but a
2226 // a really high packet rate of tiny packets is not a good idea anyway. Use bigger packets
2227 // with a lower rate. If the app is really trying to fill the pipe and blasting a large
2228 // amount of data (and not forcing us to send small packets), then our code should be sending
2229 // mostly full packets, which means that this is closer to a gap of around ~18MB.
2230 int64 nGap = ctx.m_nPktNum - m_statsEndToEnd.m_nMaxRecvPktNum;
2231 if ( nGap > 0x4000 )
2232 {
2233 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_Generic,
2234 "Pkt number lurch by %lld; %04x->%04x",
2235 (long long)nGap, (uint16)m_statsEndToEnd.m_nMaxRecvPktNum, nWireSeqNum);
2236 return false;
2237 }
2238
2239 // Decrypted ok. Track flow, and allow this packet to update the logical state, reply timeouts, etc
2240 m_statsEndToEnd.TrackRecvPacket( cbPacketSize, ctx.m_usecNow );
2241 return true;
2242 }
2243
APIAcceptConnection()2244 EResult CSteamNetworkConnectionBase::APIAcceptConnection()
2245 {
2246 AssertLocksHeldByCurrentThread();
2247
2248 // Must be in in state ready to be accepted
2249 if ( GetState() != k_ESteamNetworkingConnectionState_Connecting )
2250 {
2251 if ( GetState() == k_ESteamNetworkingConnectionState_ClosedByPeer )
2252 {
2253 SpewWarning( "[%s] Cannot accept connection; already closed by remote host.", GetDescription() );
2254 }
2255 else if ( BSymmetricMode() && BStateIsActive() )
2256 {
2257 SpewMsg( "[%s] Symmetric connection has already been accepted (perhaps implicitly, by attempting matching outbound connection)", GetDescription() );
2258 return k_EResultDuplicateRequest;
2259 }
2260 else
2261 {
2262 SpewError( "[%s] Cannot accept connection, current state is %d.", GetDescription(), GetState() );
2263 }
2264 return k_EResultInvalidState;
2265 }
2266
2267 // Should only be called for connections initiated remotely
2268 if ( !m_bConnectionInitiatedRemotely )
2269 {
2270 SpewError( "[%s] Should not be trying to acccept this connection, it was not initiated remotely.", GetDescription() );
2271 return k_EResultInvalidParam;
2272 }
2273
2274 // Select the cipher. We needed to wait until now to do it, because the app
2275 // might have set connection options on a new connection.
2276 Assert( m_eNegotiatedCipher == k_ESteamNetworkingSocketsCipher_INVALID );
2277 if ( !BFinishCryptoHandshake( true ) )
2278 return k_EResultHandshakeFailed;
2279
2280 SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
2281
2282 // Derived class knows what to do next
2283 EResult eResult = AcceptConnection( usecNow );
2284 if ( eResult == k_EResultOK )
2285 {
2286 // Make sure they properly transitioned the connection state
2287 AssertMsg2(
2288 GetState() == k_ESteamNetworkingConnectionState_FindingRoute || GetState() == k_ESteamNetworkingConnectionState_Connected,
2289 "[%s] AcceptConnection put the connection into state %d", GetDescription(), (int)GetState() );
2290 }
2291 else
2292 {
2293 // Nuke connection if we fail. (If they provided a more specific reason and already closed
2294 // the connection, this won't do anything.)
2295 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_InternalError, "Failed to accept connection." );
2296 }
2297
2298 return eResult;
2299 }
2300
AcceptConnection(SteamNetworkingMicroseconds usecNow)2301 EResult CSteamNetworkConnectionBase::AcceptConnection( SteamNetworkingMicroseconds usecNow )
2302 {
2303 NOTE_UNUSED( usecNow );
2304
2305 // You need to override this if your connection type can be accepted
2306 Assert( false );
2307 return k_EResultFail;
2308 }
2309
APICloseConnection(int nReason,const char * pszDebug,bool bEnableLinger)2310 void CSteamNetworkConnectionBase::APICloseConnection( int nReason, const char *pszDebug, bool bEnableLinger )
2311 {
2312 AssertLocksHeldByCurrentThread();
2313
2314 // If we already know the reason for the problem, we should ignore theirs
2315 if ( m_eEndReason == k_ESteamNetConnectionEnd_Invalid || GetState() == k_ESteamNetworkingConnectionState_Connecting || GetState() == k_ESteamNetworkingConnectionState_FindingRoute || GetState() == k_ESteamNetworkingConnectionState_Connected )
2316 {
2317 if ( nReason == 0 )
2318 {
2319 nReason = k_ESteamNetConnectionEnd_App_Generic;
2320 }
2321 else if ( nReason < k_ESteamNetConnectionEnd_App_Min || nReason > k_ESteamNetConnectionEnd_AppException_Max )
2322 {
2323 // Use a special value so that we can detect if people have this bug in our analytics
2324 nReason = k_ESteamNetConnectionEnd_App_Max;
2325 pszDebug = "Invalid numeric reason code";
2326 }
2327
2328 m_eEndReason = ESteamNetConnectionEnd( nReason );
2329 if ( m_szEndDebug[0] == '\0' )
2330 {
2331 if ( pszDebug == nullptr || *pszDebug == '\0' )
2332 {
2333 if ( nReason >= k_ESteamNetConnectionEnd_AppException_Min )
2334 {
2335 pszDebug = "Application closed connection in an unusual way";
2336 }
2337 else
2338 {
2339 pszDebug = "Application closed connection";
2340 }
2341 }
2342 V_strcpy_safe( m_szEndDebug, pszDebug );
2343 }
2344 }
2345
2346 // Check our state
2347 switch ( GetState() )
2348 {
2349 case k_ESteamNetworkingConnectionState_Dead:
2350 case k_ESteamNetworkingConnectionState_None:
2351 case k_ESteamNetworkingConnectionState_FinWait:
2352 case k_ESteamNetworkingConnectionState_Linger:
2353 default:
2354 Assert( false );
2355 return;
2356
2357 case k_ESteamNetworkingConnectionState_ClosedByPeer:
2358 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
2359 SpewVerbose( "[%s] cleaned up by app\n", GetDescription() );
2360 ConnectionState_FinWait();
2361 break;
2362
2363 case k_ESteamNetworkingConnectionState_Connecting:
2364 case k_ESteamNetworkingConnectionState_FindingRoute:
2365 SpewMsg( "[%s] closed by app before we got connected (%d) %s\n", GetDescription(), (int)m_eEndReason, m_szEndDebug );
2366 ConnectionState_FinWait();
2367 break;
2368
2369 case k_ESteamNetworkingConnectionState_Connected:
2370 if ( bEnableLinger )
2371 {
2372 SpewMsg( "[%s] closed by app, entering linger state (%d) %s\n", GetDescription(), (int)m_eEndReason, m_szEndDebug );
2373 SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
2374 SetState( k_ESteamNetworkingConnectionState_Linger, usecNow );
2375 CheckConnectionStateAndSetNextThinkTime( usecNow );
2376 }
2377 else
2378 {
2379 SpewMsg( "[%s] closed by app (%d) %s\n", GetDescription(), (int)m_eEndReason, m_szEndDebug );
2380 ConnectionState_FinWait();
2381 }
2382 break;
2383 }
2384 }
2385
SetState(ESteamNetworkingConnectionState eNewState,SteamNetworkingMicroseconds usecNow)2386 void CSteamNetworkConnectionBase::SetState( ESteamNetworkingConnectionState eNewState, SteamNetworkingMicroseconds usecNow )
2387 {
2388 // All connection state transitions require the global lock!
2389 AssertLocksHeldByCurrentThread();
2390
2391 if ( eNewState == m_eConnectionState )
2392 return;
2393 const ESteamNetworkingConnectionState eOldState = m_eConnectionState;
2394 m_eConnectionState = eNewState;
2395
2396 // Remember when we entered this state
2397 m_usecWhenEnteredConnectionState = usecNow;
2398
2399 // Set wire state
2400 switch ( GetState() )
2401 {
2402 default:
2403 Assert( false );
2404 // FALLTHROUGH
2405
2406 case k_ESteamNetworkingConnectionState_Dead:
2407 case k_ESteamNetworkingConnectionState_None:
2408 case k_ESteamNetworkingConnectionState_Connected:
2409 case k_ESteamNetworkingConnectionState_FindingRoute:
2410 case k_ESteamNetworkingConnectionState_Connecting:
2411 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
2412 case k_ESteamNetworkingConnectionState_ClosedByPeer:
2413 m_eConnectionWireState = eNewState;
2414 break;
2415
2416 case k_ESteamNetworkingConnectionState_FinWait:
2417
2418 // Check where we are coming from
2419 switch ( eOldState )
2420 {
2421 case k_ESteamNetworkingConnectionState_Dead:
2422 case k_ESteamNetworkingConnectionState_None:
2423 case k_ESteamNetworkingConnectionState_FinWait:
2424 default:
2425 Assert( false );
2426 break;
2427
2428 case k_ESteamNetworkingConnectionState_ClosedByPeer:
2429 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
2430 Assert( m_eConnectionWireState == eOldState );
2431 m_eConnectionWireState = eOldState;
2432 break;
2433
2434 case k_ESteamNetworkingConnectionState_Linger:
2435 case k_ESteamNetworkingConnectionState_Connecting:
2436 case k_ESteamNetworkingConnectionState_FindingRoute:
2437 case k_ESteamNetworkingConnectionState_Connected:
2438 m_eConnectionWireState = k_ESteamNetworkingConnectionState_FinWait;
2439 break;
2440 }
2441 break;
2442
2443 case k_ESteamNetworkingConnectionState_Linger:
2444 Assert( eOldState == k_ESteamNetworkingConnectionState_Connected );
2445 m_eConnectionWireState = k_ESteamNetworkingConnectionState_Connected;
2446 break;
2447 }
2448
2449 // Certain connection options cannot be changed after a certain point
2450 bool bLock = false;
2451 if ( GetState() == k_ESteamNetworkingConnectionState_Connecting )
2452 {
2453 if ( m_bConnectionInitiatedRemotely )
2454 {
2455 // Remote host initiated the connection. All options below can be tweaked
2456 // until
2457 }
2458 else
2459 {
2460 // We initiated the connection. All options listed below must be set at creation time
2461 bLock = true;
2462 }
2463 }
2464 else if ( BStateIsActive() )
2465 {
2466 bLock = true;
2467 }
2468 if ( bLock )
2469 {
2470 // Can't change certain options after this point
2471 m_connectionConfig.m_IP_AllowWithoutAuth.Lock();
2472 m_connectionConfig.m_Unencrypted.Lock();
2473 m_connectionConfig.m_SymmetricConnect.Lock();
2474 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
2475 m_connectionConfig.m_SDRClient_DebugTicketAddress.Lock();
2476 #endif
2477 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
2478 m_connectionConfig.m_P2P_Transport_ICE_Enable.Lock();
2479 #endif
2480 }
2481
2482 // Post a notification when certain state changes occur. Note that
2483 // "internal" state changes, where the connection is effectively closed
2484 // from the application's perspective, are not relevant
2485 const ESteamNetworkingConnectionState eOldAPIState = CollapseConnectionStateToAPIState( eOldState );
2486 const ESteamNetworkingConnectionState eNewAPIState = CollapseConnectionStateToAPIState( GetState() );
2487
2488 // Internal connection used by the higher-level messages interface?
2489 Assert( m_nSupressStateChangeCallbacks >= 0 );
2490 if ( m_nSupressStateChangeCallbacks == 0 )
2491 {
2492 if ( eOldState == k_ESteamNetworkingConnectionState_None && GetState() == k_ESteamNetworkingConnectionState_ProblemDetectedLocally )
2493 {
2494 // Do not post callbacks for internal failures during connection creation
2495 }
2496 else
2497 {
2498 PostConnectionStateChangedCallback( eOldAPIState, eNewAPIState );
2499 }
2500 }
2501
2502 // Any time we switch into a state that is closed from an API perspective,
2503 // discard any unread received messages
2504 if ( eNewAPIState == k_ESteamNetworkingConnectionState_None )
2505 {
2506 g_lockAllRecvMessageQueues.lock();
2507 m_queueRecvMessages.PurgeMessages();
2508 g_lockAllRecvMessageQueues.unlock();
2509 }
2510
2511 // Slam some stuff when we are in various states
2512 switch ( GetState() )
2513 {
2514 case k_ESteamNetworkingConnectionState_Dead:
2515 case k_ESteamNetworkingConnectionState_None:
2516 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
2517 case k_ESteamNetworkingConnectionState_FinWait:
2518 case k_ESteamNetworkingConnectionState_ClosedByPeer:
2519
2520 // Check for leaving connected state
2521 if ( m_statsEndToEnd.m_usecWhenStartedConnectedState != 0 && m_statsEndToEnd.m_usecWhenEndedConnectedState == 0 )
2522 m_statsEndToEnd.m_usecWhenEndedConnectedState = usecNow;
2523
2524 // Let stats tracking system know that it shouldn't
2525 // expect to be able to get stuff acked, etc
2526 m_statsEndToEnd.SetPassive( true, m_usecWhenEnteredConnectionState );
2527
2528 // Go head and free up memory now
2529 SNP_ShutdownConnection();
2530 break;
2531
2532 case k_ESteamNetworkingConnectionState_Linger:
2533 case k_ESteamNetworkingConnectionState_Connected:
2534
2535 // Key exchange should be complete
2536 Assert( m_bCryptKeysValid );
2537 Assert( m_statsEndToEnd.m_usecWhenStartedConnectedState != 0 );
2538
2539 // Link stats tracker should send and expect, acks, keepalives, etc
2540 m_statsEndToEnd.SetPassive( false, m_usecWhenEnteredConnectionState );
2541 break;
2542
2543 case k_ESteamNetworkingConnectionState_FindingRoute:
2544
2545 // Key exchange should be complete. (We do that when accepting a connection.)
2546 Assert( m_bCryptKeysValid );
2547
2548 // FIXME. Probably we should NOT set the stats tracker as "active" yet.
2549 //Assert( m_statsEndToEnd.IsPassive() );
2550 m_statsEndToEnd.SetPassive( false, m_usecWhenEnteredConnectionState );
2551 break;
2552
2553 case k_ESteamNetworkingConnectionState_Connecting:
2554
2555 // And we shouldn't mark stats object as ready until we go connected
2556 Assert( m_statsEndToEnd.IsPassive() );
2557 break;
2558 }
2559
2560 // Finally, hook for derived class to take action. But not if we're dead
2561 switch ( GetState() )
2562 {
2563 case k_ESteamNetworkingConnectionState_Dead:
2564 case k_ESteamNetworkingConnectionState_None:
2565 break;
2566 default:
2567 ConnectionStateChanged( eOldState );
2568 break;
2569 }
2570 }
2571
ConnectionStateChanged(ESteamNetworkingConnectionState eOldState)2572 void CSteamNetworkConnectionBase::ConnectionStateChanged( ESteamNetworkingConnectionState eOldState )
2573 {
2574
2575 // If we have a transport, give it a chance to react to the state change
2576 if ( m_pTransport )
2577 m_pTransport->TransportConnectionStateChanged( eOldState );
2578 }
2579
ReceivedMessage(const void * pData,int cbData,int64 nMsgNum,int nFlags,SteamNetworkingMicroseconds usecNow)2580 bool CSteamNetworkConnectionBase::ReceivedMessage( const void *pData, int cbData, int64 nMsgNum, int nFlags, SteamNetworkingMicroseconds usecNow )
2581 {
2582 // // !TEST! Enable this during connection test to trap bogus messages earlier
2583 // struct TestMsg
2584 // {
2585 // int64 m_nMsgNum;
2586 // bool m_bReliable;
2587 // int m_cbSize;
2588 // uint8 m_data[ 20*1000 ];
2589 // };
2590 // const TestMsg *pTestMsg = (const TestMsg *)pData;
2591 //
2592 // // Size makes sense?
2593 // Assert( sizeof(*pTestMsg) - sizeof(pTestMsg->m_data) + pTestMsg->m_cbSize == cbData );
2594
2595 // Create a message
2596 CSteamNetworkingMessage *pMsg = CSteamNetworkingMessage::New( this, cbData, nMsgNum, nFlags, usecNow );
2597 if ( !pMsg )
2598 {
2599 // Hm. this failure really is probably a sign that we are in a pretty bad state,
2600 // and we are unlikely to recover. Should we just abort the connection?
2601 // Right now, we'll try to muddle on.
2602 return false;
2603 }
2604
2605 // Copy the data
2606 memcpy( pMsg->m_pData, pData, cbData );
2607
2608 // Receive it
2609 ReceivedMessage( pMsg );
2610
2611 return true;
2612 }
2613
ReceivedMessage(CSteamNetworkingMessage * pMsg)2614 void CSteamNetworkConnectionBase::ReceivedMessage( CSteamNetworkingMessage *pMsg )
2615 {
2616 m_pLock->AssertHeldByCurrentThread();
2617
2618 SpewVerboseGroup( m_connectionConfig.m_LogLevel_Message.Get(), "[%s] RecvMessage MsgNum=%lld sz=%d\n",
2619 GetDescription(),
2620 (long long)pMsg->m_nMessageNumber,
2621 pMsg->m_cbSize );
2622
2623 // We use the same lock to protect *all* recv queues, for both connections and poll groups,
2624 // which keeps this really simple.
2625 g_lockAllRecvMessageQueues.lock();
2626
2627 // Add to end of my queue.
2628 pMsg->LinkToQueueTail( &CSteamNetworkingMessage::m_links, &m_queueRecvMessages );
2629
2630 // Add to the poll group, if we are in one
2631 if ( m_pPollGroup )
2632 pMsg->LinkToQueueTail( &CSteamNetworkingMessage::m_linksSecondaryQueue, &m_pPollGroup->m_queueRecvMessages );
2633
2634 g_lockAllRecvMessageQueues.unlock();
2635 }
2636
PostConnectionStateChangedCallback(ESteamNetworkingConnectionState eOldAPIState,ESteamNetworkingConnectionState eNewAPIState)2637 void CSteamNetworkConnectionBase::PostConnectionStateChangedCallback( ESteamNetworkingConnectionState eOldAPIState, ESteamNetworkingConnectionState eNewAPIState )
2638 {
2639 // Send API callback for this state change?
2640 // Do not post if connection state has not changed from an API perspective
2641 if ( eOldAPIState != eNewAPIState )
2642 {
2643
2644 SteamNetConnectionStatusChangedCallback_t c;
2645 ConnectionPopulateInfo( c.m_info );
2646 c.m_eOldState = eOldAPIState;
2647 c.m_hConn = m_hConnectionSelf;
2648
2649 // !KLUDGE! For ISteamnetworkingMessages connections, we want to process the callback immediately.
2650 void *fnCallback = m_connectionConfig.m_Callback_ConnectionStatusChanged.Get();
2651 if ( IsConnectionForMessagesSession() )
2652 {
2653 if ( fnCallback )
2654 {
2655 FnSteamNetConnectionStatusChanged fnConnectionStatusChanged = (FnSteamNetConnectionStatusChanged)( fnCallback );
2656 (*fnConnectionStatusChanged)( &c );
2657 }
2658 else
2659 {
2660 // Currently there is no use case that does this. It's probably a bug.
2661 Assert( false );
2662 }
2663 }
2664 else
2665 {
2666
2667 // Typical codepath - post to a queue
2668 m_pSteamNetworkingSocketsInterface->QueueCallback( c, fnCallback );
2669 }
2670 }
2671
2672 // Send diagnostics for this state change?
2673 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_DIAGNOSTICSUI
2674 if (
2675 m_pSteamNetworkingSocketsInterface->m_pSteamNetworkingUtils->m_usecConnectionUpdateFrequency == 0 // Not enabled globally right now
2676 || ( m_usecWhenNextDiagnosticsUpdate == k_nThinkTime_Never && !BStateIsActive() ) // We've sent a terminal state change. This transition isn't interesting from a diagnostic standpoint.
2677 ) {
2678 // Disabled, don't send any more
2679 m_usecWhenNextDiagnosticsUpdate = k_nThinkTime_Never;
2680 }
2681 else
2682 {
2683 // Post an update. If more should be sent, we'll schedule it.
2684 // NOTE: Here we are going to ask the connection to populate SteamNetConnectionInfo_t info
2685 // *again*, even though we just called ConnectionPopulateInfo above. But this keeps the code
2686 // simpler and connectoin state changes are infrequent, relatively speaking
2687 SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
2688 m_pSteamNetworkingSocketsInterface->m_pSteamNetworkingUtils->PostConnectionStateUpdateForDiagnosticsUI( eOldAPIState, this, usecNow );
2689 Assert( m_usecWhenNextDiagnosticsUpdate > usecNow );
2690
2691 // If we might need to send another, schedule a wakeup call
2692 EnsureMinThinkTime( m_usecWhenNextDiagnosticsUpdate );
2693 }
2694 #endif
2695 }
2696
2697 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_DIAGNOSTICSUI
CheckScheduleDiagnosticsUpdateASAP()2698 void CSteamNetworkConnectionBase::CheckScheduleDiagnosticsUpdateASAP()
2699 {
2700 if ( m_pSteamNetworkingSocketsInterface->m_pSteamNetworkingUtils->m_usecConnectionUpdateFrequency <= 0 )
2701 {
2702 m_usecWhenNextDiagnosticsUpdate = k_nThinkTime_Never;
2703 }
2704 else if ( m_usecWhenNextDiagnosticsUpdate == k_nThinkTime_Never )
2705 {
2706 // We've sent out last update. Don't send any more
2707 }
2708 else
2709 {
2710 m_usecWhenNextDiagnosticsUpdate = k_nThinkTime_ASAP;
2711 SetNextThinkTimeASAP();
2712 }
2713 }
2714
2715 // FIXME - Should we just move this into SteamNetConnectionInfo_t?
2716 // Seems like maybe we should just provide a localized result directly
2717 // there.
GetConnectionStateLocToken(ESteamNetworkingConnectionState eOldConnState,ESteamNetworkingConnectionState eState,int nEndReason)2718 static const char *GetConnectionStateLocToken( ESteamNetworkingConnectionState eOldConnState, ESteamNetworkingConnectionState eState, int nEndReason )
2719 {
2720 if ( eState == k_ESteamNetworkingConnectionState_Connecting )
2721 return "#SteamNetSockets_Connecting";
2722 if ( eState == k_ESteamNetworkingConnectionState_FindingRoute )
2723 return "#SteamNetSockets_FindingRoute";
2724 if ( eState == k_ESteamNetworkingConnectionState_Connected )
2725 return "#SteamNetSockets_Connected";
2726
2727 if ( nEndReason == k_ESteamNetConnectionEnd_Misc_Timeout )
2728 {
2729 if ( eOldConnState == k_ESteamNetworkingConnectionState_Connecting )
2730 return "#SteamNetSockets_Disconnect_ConnectionTimedout";
2731 return "#SteamNetSockets_Disconnect_TimedOut";
2732 }
2733 if ( eState == k_ESteamNetworkingConnectionState_ProblemDetectedLocally )
2734 {
2735 if ( nEndReason >= k_ESteamNetConnectionEnd_Local_Min && nEndReason <= k_ESteamNetConnectionEnd_Local_Max )
2736 {
2737 if ( nEndReason == k_ESteamNetConnectionEnd_Local_ManyRelayConnectivity )
2738 return "#SteamNetSockets_Disconnect_LocalProblem_ManyRelays";
2739 if ( nEndReason == k_ESteamNetConnectionEnd_Local_HostedServerPrimaryRelay )
2740 return "#SteamNetSockets_Disconnect_LocalProblem_HostedServerPrimaryRelay";
2741 if ( nEndReason == k_ESteamNetConnectionEnd_Local_NetworkConfig )
2742 return "#SteamNetSockets_Disconnect_LocalProblem_NetworkConfig";
2743 return "#SteamNetSockets_Disconnect_LocalProblem_Other";
2744 }
2745
2746 if ( nEndReason >= k_ESteamNetConnectionEnd_Remote_Min && nEndReason <= k_ESteamNetConnectionEnd_Remote_Max )
2747 {
2748 if ( nEndReason == k_ESteamNetConnectionEnd_Remote_Timeout )
2749 {
2750 if ( eOldConnState == k_ESteamNetworkingConnectionState_Connecting )
2751 return "#SteamNetSockets_Disconnect_RemoteProblem_TimeoutConnecting";
2752 return "#SteamNetSockets_Disconnect_RemoteProblem_Timeout";
2753 }
2754
2755 if ( nEndReason == k_ESteamNetConnectionEnd_Remote_BadCrypt )
2756 return "#SteamNetSockets_Disconnect_RemoteProblem_BadCrypt";
2757 if ( nEndReason == k_ESteamNetConnectionEnd_Remote_BadCert )
2758 return "#SteamNetSockets_Disconnect_RemoteProblem_BadCert";
2759 }
2760
2761 if ( nEndReason == k_ESteamNetConnectionEnd_Misc_P2P_Rendezvous )
2762 return "#SteamNetSockets_Disconnect_P2P_Rendezvous";
2763
2764 if ( nEndReason == k_ESteamNetConnectionEnd_Misc_SteamConnectivity )
2765 return "#SteamNetSockets_Disconnect_SteamConnectivity";
2766
2767 if ( nEndReason == k_ESteamNetConnectionEnd_Misc_InternalError )
2768 return "#SteamNetSockets_Disconnect_InternalError";
2769
2770 return "#SteamNetSockets_Disconnect_Unusual";
2771 }
2772 if ( eState == k_ESteamNetworkingConnectionState_ClosedByPeer )
2773 {
2774 if ( nEndReason >= k_ESteamNetConnectionEnd_Local_Min && nEndReason <= k_ESteamNetConnectionEnd_Local_Max )
2775 return "#SteamNetSockets_PeerClose_LocalProblem";
2776
2777 if ( nEndReason >= k_ESteamNetConnectionEnd_Remote_Min && nEndReason <= k_ESteamNetConnectionEnd_Remote_Max )
2778 {
2779 if ( nEndReason == k_ESteamNetConnectionEnd_Remote_BadCrypt )
2780 return "#SteamNetSockets_PeerClose_RemoteProblem_BadCrypt";
2781 if ( nEndReason == k_ESteamNetConnectionEnd_Remote_BadCert )
2782 return "#SteamNetSockets_PeerClose_RemoteProblem_BadCert";
2783 // Peer closed the connection, and they think it's our fault somehow?
2784 }
2785
2786 if ( nEndReason >= k_ESteamNetConnectionEnd_App_Min && nEndReason <= k_ESteamNetConnectionEnd_App_Max )
2787 {
2788 return "#SteamNetSockets_PeerClose_App_Normal";
2789 }
2790 if ( nEndReason >= k_ESteamNetConnectionEnd_AppException_Min && nEndReason <= k_ESteamNetConnectionEnd_AppException_Max )
2791 {
2792 return "#SteamNetSockets_PeerClose_App_Unusual";
2793 }
2794 return "#SteamNetSockets_PeerClose_Ununusual";
2795 }
2796
2797 if ( nEndReason >= k_ESteamNetConnectionEnd_App_Min && nEndReason <= k_ESteamNetConnectionEnd_App_Max )
2798 {
2799 return "#SteamNetSockets_AppClose_Normal";
2800 }
2801 if ( nEndReason >= k_ESteamNetConnectionEnd_AppException_Min && nEndReason <= k_ESteamNetConnectionEnd_AppException_Max )
2802 {
2803 return "#SteamNetSockets_AppClose_Unusual";
2804 }
2805
2806 return "";
2807 }
2808
ConnectionPopulateDiagnostics(ESteamNetworkingConnectionState eOldState,CGameNetworkingUI_ConnectionState & msgConnectionState,SteamNetworkingMicroseconds usecNow)2809 void CSteamNetworkConnectionBase::ConnectionPopulateDiagnostics( ESteamNetworkingConnectionState eOldState, CGameNetworkingUI_ConnectionState &msgConnectionState, SteamNetworkingMicroseconds usecNow )
2810 {
2811 msgConnectionState.set_start_time( (usecNow - m_usecWhenCreated)/k_nMillion );
2812
2813 // Use the API function to populate the struct
2814 SteamNetworkingDetailedConnectionStatus stats;
2815 APIGetDetailedConnectionStatus( stats, usecNow );
2816
2817 // Fill in diagnostic fields that correspond to SteamNetConnectionInfo_t
2818 if ( stats.m_eTransportKind != k_ESteamNetTransport_Unknown )
2819 msgConnectionState.set_transport_kind( stats.m_eTransportKind );
2820 if ( stats.m_info.m_idPOPRelay )
2821 msgConnectionState.set_sdrpopid_local( SteamNetworkingPOPIDRender( stats.m_info.m_idPOPRelay ).c_str() );
2822 if ( stats.m_info.m_idPOPRemote )
2823 msgConnectionState.set_sdrpopid_remote( SteamNetworkingPOPIDRender( stats.m_info.m_idPOPRemote ).c_str() );
2824 if ( !stats.m_info.m_addrRemote.IsIPv6AllZeros() )
2825 msgConnectionState.set_address_remote( SteamNetworkingIPAddrRender( stats.m_info.m_addrRemote ).c_str() );
2826
2827 msgConnectionState.set_connection_id_local( m_unConnectionIDLocal );
2828 msgConnectionState.set_identity_local( SteamNetworkingIdentityRender( m_identityLocal ).c_str() );
2829 if ( !m_identityRemote.IsInvalid() && m_identityRemote.m_eType != k_ESteamNetworkingIdentityType_IPAddress )
2830 msgConnectionState.set_identity_remote( SteamNetworkingIdentityRender( m_identityRemote ).c_str() );
2831 switch ( GetState() )
2832 {
2833 default:
2834 Assert( false );
2835 case k_ESteamNetworkingConnectionState_Connecting:
2836 case k_ESteamNetworkingConnectionState_FindingRoute:
2837 case k_ESteamNetworkingConnectionState_Connected:
2838 case k_ESteamNetworkingConnectionState_ClosedByPeer:
2839 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
2840 case k_ESteamNetworkingConnectionState_Linger:
2841 msgConnectionState.set_connection_state( GetState() );
2842 break;
2843
2844 case k_ESteamNetworkingConnectionState_None:
2845 case k_ESteamNetworkingConnectionState_FinWait:
2846 case k_ESteamNetworkingConnectionState_Dead:
2847 // Use last public state
2848 msgConnectionState.set_connection_state( GetWireState() );
2849 break;
2850 }
2851
2852 msgConnectionState.set_status_loc_token( GetConnectionStateLocToken( eOldState, stats.m_info.m_eState, stats.m_info.m_eEndReason ) );
2853
2854 // Check if we've been closed, and schedule the next diagnostics update
2855 if ( m_eEndReason != k_ESteamNetConnectionEnd_Invalid )
2856 {
2857 msgConnectionState.set_close_reason( GetConnectionEndReason() );
2858 msgConnectionState.set_close_message( GetConnectionEndDebugString() );
2859 }
2860 if ( BStateIsActive() )
2861 {
2862 m_usecWhenNextDiagnosticsUpdate = usecNow + m_pSteamNetworkingSocketsInterface->m_pSteamNetworkingUtils->m_usecConnectionUpdateFrequency;
2863 }
2864 else
2865 {
2866 m_usecWhenNextDiagnosticsUpdate = k_nThinkTime_Never;
2867 }
2868
2869 // end-to-end stats measured locally
2870 LinkStatsInstantaneousStructToMsg( stats.m_statsEndToEnd.m_latest, *msgConnectionState.mutable_e2e_quality_local()->mutable_instantaneous() );
2871 LinkStatsLifetimeStructToMsg( stats.m_statsEndToEnd.m_lifetime, *msgConnectionState.mutable_e2e_quality_local()->mutable_lifetime() );
2872
2873 // end-to-end stats from remote host, if any
2874 if ( stats.m_statsEndToEnd.m_flAgeLatestRemote >= 0.0f )
2875 {
2876 msgConnectionState.set_e2e_quality_remote_instantaneous_time( uint64( stats.m_statsEndToEnd.m_flAgeLatestRemote * 1e6 ) );
2877 LinkStatsInstantaneousStructToMsg( stats.m_statsEndToEnd.m_latestRemote, *msgConnectionState.mutable_e2e_quality_remote()->mutable_instantaneous() );
2878 }
2879 if ( stats.m_statsEndToEnd.m_flAgeLifetimeRemote >= 0.0f )
2880 {
2881 msgConnectionState.set_e2e_quality_remote_lifetime_time( uint64( stats.m_statsEndToEnd.m_flAgeLifetimeRemote * 1e6 ) );
2882 LinkStatsLifetimeStructToMsg( stats.m_statsEndToEnd.m_lifetimeRemote, *msgConnectionState.mutable_e2e_quality_remote()->mutable_lifetime() );
2883 }
2884
2885 if ( !stats.m_addrPrimaryRouter.IsIPv6AllZeros() && stats.m_statsPrimaryRouter.m_lifetime.m_nPktsRecvSequenced > 0 )
2886 {
2887 LinkStatsInstantaneousStructToMsg( stats.m_statsPrimaryRouter.m_latest, *msgConnectionState.mutable_front_quality_local()->mutable_instantaneous() );
2888 LinkStatsLifetimeStructToMsg( stats.m_statsPrimaryRouter.m_lifetime, *msgConnectionState.mutable_front_quality_local()->mutable_lifetime() );
2889
2890 if ( stats.m_statsPrimaryRouter.m_flAgeLatestRemote >= 0.0f )
2891 {
2892 msgConnectionState.set_front_quality_remote_instantaneous_time( uint64( stats.m_statsPrimaryRouter.m_flAgeLatestRemote * 1e6 ) );
2893 LinkStatsInstantaneousStructToMsg( stats.m_statsPrimaryRouter.m_latestRemote, *msgConnectionState.mutable_front_quality_remote()->mutable_instantaneous() );
2894 }
2895 if ( stats.m_statsPrimaryRouter.m_flAgeLifetimeRemote >= 0.0f )
2896 {
2897 msgConnectionState.set_front_quality_remote_lifetime_time( uint64( stats.m_statsPrimaryRouter.m_flAgeLifetimeRemote * 1e6 ) );
2898 LinkStatsLifetimeStructToMsg( stats.m_statsPrimaryRouter.m_lifetimeRemote, *msgConnectionState.mutable_front_quality_remote()->mutable_lifetime() );
2899 }
2900 }
2901
2902 // If any selected transport, give them a chance to fill in info
2903 if ( m_pTransport )
2904 m_pTransport->TransportPopulateDiagnostics( msgConnectionState, usecNow );
2905 }
2906
2907 #endif
2908
ConnectionState_ProblemDetectedLocally(ESteamNetConnectionEnd eReason,const char * pszFmt,...)2909 void CSteamNetworkConnectionBase::ConnectionState_ProblemDetectedLocally( ESteamNetConnectionEnd eReason, const char *pszFmt, ... )
2910 {
2911 AssertLocksHeldByCurrentThread();
2912
2913 va_list ap;
2914
2915 SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
2916 Assert( eReason > k_ESteamNetConnectionEnd_AppException_Max );
2917 Assert( pszFmt && *pszFmt );
2918 if ( m_eEndReason == k_ESteamNetConnectionEnd_Invalid || GetState() == k_ESteamNetworkingConnectionState_Linger )
2919 {
2920 m_eEndReason = eReason;
2921 va_start(ap, pszFmt);
2922 V_vsprintf_safe( m_szEndDebug, pszFmt, ap );
2923 va_end(ap);
2924 }
2925
2926 // Check our state
2927 switch ( GetState() )
2928 {
2929 case k_ESteamNetworkingConnectionState_Dead:
2930 case k_ESteamNetworkingConnectionState_None:
2931 default:
2932 AssertMsg( false, "[%s] problem (%d) %s, but connection already dead (%d %d %s)",
2933 GetDescription(), (int)eReason, pszFmt, GetState(), (int)m_eEndReason, m_szEndDebug );
2934 return;
2935
2936 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
2937 case k_ESteamNetworkingConnectionState_FinWait:
2938 case k_ESteamNetworkingConnectionState_ClosedByPeer:
2939 // Don't do anything
2940 break;
2941
2942 case k_ESteamNetworkingConnectionState_Linger:
2943 ConnectionState_FinWait();
2944 return;
2945
2946 case k_ESteamNetworkingConnectionState_Connecting:
2947 case k_ESteamNetworkingConnectionState_FindingRoute:
2948 case k_ESteamNetworkingConnectionState_Connected:
2949
2950 SpewMsg( "[%s] problem detected locally (%d): %s\n", GetDescription(), (int)m_eEndReason, m_szEndDebug );
2951
2952 SetState( k_ESteamNetworkingConnectionState_ProblemDetectedLocally, usecNow );
2953 break;
2954 }
2955
2956 // We don't have enough context to know if it's safe to act now. Just schedule an immediate
2957 // wake up call so we will take action at the next safe time
2958 SetNextThinkTimeASAP();
2959 }
2960
ConnectionState_FinWait()2961 void CSteamNetworkConnectionBase::ConnectionState_FinWait()
2962 {
2963 SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
2964
2965 // Check our state
2966 switch ( GetState() )
2967 {
2968 case k_ESteamNetworkingConnectionState_Dead:
2969 case k_ESteamNetworkingConnectionState_None:
2970 default:
2971 Assert( false );
2972 return;
2973
2974 case k_ESteamNetworkingConnectionState_FinWait:
2975 break;
2976
2977 case k_ESteamNetworkingConnectionState_ClosedByPeer:
2978 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
2979 case k_ESteamNetworkingConnectionState_Linger:
2980 case k_ESteamNetworkingConnectionState_Connecting:
2981 case k_ESteamNetworkingConnectionState_FindingRoute:
2982 case k_ESteamNetworkingConnectionState_Connected:
2983 SetState( k_ESteamNetworkingConnectionState_FinWait, usecNow );
2984
2985 // We don't have enough context to know if it's safe to act now. Just schedule an immediate
2986 // wake up call so we will take action at the next safe time
2987 SetNextThinkTimeASAP();
2988 break;
2989 }
2990 }
2991
ConnectionState_ClosedByPeer(int nReason,const char * pszDebug)2992 void CSteamNetworkConnectionBase::ConnectionState_ClosedByPeer( int nReason, const char *pszDebug )
2993 {
2994
2995 // Check our state
2996 switch ( m_eConnectionState )
2997 {
2998 case k_ESteamNetworkingConnectionState_Dead:
2999 case k_ESteamNetworkingConnectionState_None:
3000 default:
3001 Assert( false );
3002 return;
3003
3004 case k_ESteamNetworkingConnectionState_FinWait:
3005 // Keep hanging out until the fin wait time is up
3006 break;
3007
3008 case k_ESteamNetworkingConnectionState_Linger:
3009 // Hang out to gracefully handle any last stray packets,
3010 // clean up relay sessions, etc.
3011 ConnectionState_FinWait();
3012 break;
3013
3014 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
3015 // Just ignore this. We detected a problem, but now the peer
3016 // is also trying to close the connection. In any case, we
3017 // need to wait for the client code to close the handle
3018 break;
3019
3020 case k_ESteamNetworkingConnectionState_ClosedByPeer:
3021 // We already knew this, we're just waiting for
3022 // the client code to clean up the handle.
3023 break;
3024
3025 case k_ESteamNetworkingConnectionState_Connecting:
3026 case k_ESteamNetworkingConnectionState_FindingRoute:
3027 case k_ESteamNetworkingConnectionState_Connected:
3028
3029 if ( pszDebug && *pszDebug )
3030 V_strcpy_safe( m_szEndDebug, pszDebug );
3031 else if ( m_szEndDebug[0] == '\0' )
3032 V_strcpy_safe( m_szEndDebug, "The remote host closed the connection." );
3033 m_eEndReason = ESteamNetConnectionEnd( nReason );
3034
3035 SpewMsg( "[%s] closed by peer (%d): %s\n", GetDescription(), (int)m_eEndReason, m_szEndDebug );
3036
3037 SetState( k_ESteamNetworkingConnectionState_ClosedByPeer, SteamNetworkingSockets_GetLocalTimestamp() );
3038 break;
3039 }
3040 }
3041
BConnectionState_Connecting(SteamNetworkingMicroseconds usecNow,SteamNetworkingErrMsg & errMsg)3042 bool CSteamNetworkConnectionBase::BConnectionState_Connecting( SteamNetworkingMicroseconds usecNow, SteamNetworkingErrMsg &errMsg )
3043 {
3044 // Already failed (and they didn't handle it)? We should only transition to this state from the initial state
3045 if ( GetState() != k_ESteamNetworkingConnectionState_None )
3046 {
3047 V_sprintf_safe( errMsg, "Unexpected state %d", GetState() );
3048 AssertMsg( false, "[%s] %s", GetDescription(), errMsg );
3049 return false;
3050 }
3051
3052 // Check if symmetric mode is being requested, but doesn't make sense
3053 if ( BSymmetricMode() )
3054 {
3055 if ( !BSupportsSymmetricMode() )
3056 {
3057 V_strcpy_safe( errMsg, "SymmetricConnect not supported" );
3058 return false;
3059 }
3060 if ( m_identityRemote.IsInvalid() )
3061 {
3062 V_strcpy_safe( errMsg, "Remote identity must be known to use symmetric mode" );
3063 AssertMsg( false, errMsg );
3064 return false;
3065 }
3066 }
3067
3068 // Set the state
3069 SetState( k_ESteamNetworkingConnectionState_Connecting, usecNow );
3070
3071 // Schedule a wakeup call ASAP so we can start sending out packets immediately
3072 SetNextThinkTimeASAP();
3073
3074 // OK
3075 return true;
3076 }
3077
ConnectionState_Connected(SteamNetworkingMicroseconds usecNow)3078 void CSteamNetworkConnectionBase::ConnectionState_Connected( SteamNetworkingMicroseconds usecNow )
3079 {
3080 // Check our state
3081 switch ( GetState() )
3082 {
3083 case k_ESteamNetworkingConnectionState_Dead:
3084 case k_ESteamNetworkingConnectionState_None:
3085 case k_ESteamNetworkingConnectionState_FinWait:
3086 case k_ESteamNetworkingConnectionState_Linger:
3087 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
3088 case k_ESteamNetworkingConnectionState_ClosedByPeer:
3089 default:
3090 Assert( false );
3091 break;
3092
3093 case k_ESteamNetworkingConnectionState_Connecting:
3094 case k_ESteamNetworkingConnectionState_FindingRoute:
3095 {
3096 // We must receive a packet in order to be connected!
3097 Assert( m_statsEndToEnd.m_usecTimeLastRecv > 0 );
3098
3099 // Should only enter this state once
3100 Assert( m_statsEndToEnd.m_usecWhenStartedConnectedState == 0 );
3101 m_statsEndToEnd.m_usecWhenStartedConnectedState = usecNow;
3102
3103 // Spew, if this is newsworthy
3104 if ( !m_bConnectionInitiatedRemotely || GetState() == k_ESteamNetworkingConnectionState_FindingRoute )
3105 SpewMsg( "[%s] connected\n", GetDescription() );
3106
3107 SetState( k_ESteamNetworkingConnectionState_Connected, usecNow );
3108
3109 SNP_InitializeConnection( usecNow );
3110 }
3111
3112 break;
3113
3114 case k_ESteamNetworkingConnectionState_Connected:
3115 break;
3116 }
3117
3118 // Schedule a wakeup call ASAP so we can start sending out packets immediately
3119 SetNextThinkTimeASAP();
3120 }
3121
ConnectionState_FindingRoute(SteamNetworkingMicroseconds usecNow)3122 void CSteamNetworkConnectionBase::ConnectionState_FindingRoute( SteamNetworkingMicroseconds usecNow )
3123 {
3124 // Check our state, we really should only transition into this state from one state.
3125 switch ( GetState() )
3126 {
3127 case k_ESteamNetworkingConnectionState_Dead:
3128 case k_ESteamNetworkingConnectionState_None:
3129 case k_ESteamNetworkingConnectionState_FinWait:
3130 case k_ESteamNetworkingConnectionState_Linger:
3131 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
3132 case k_ESteamNetworkingConnectionState_ClosedByPeer:
3133 case k_ESteamNetworkingConnectionState_Connected:
3134 default:
3135 Assert( false );
3136 break;
3137
3138 case k_ESteamNetworkingConnectionState_Connecting:
3139
3140 // Spew, if this is newsworthy
3141 if ( !m_bConnectionInitiatedRemotely )
3142 SpewMsg( "[%s] finding route\n", GetDescription() );
3143
3144 SetState( k_ESteamNetworkingConnectionState_FindingRoute, usecNow );
3145 break;
3146
3147 case k_ESteamNetworkingConnectionState_FindingRoute:
3148 break;
3149 }
3150
3151 // Schedule a wakeup call ASAP so we can start sending out packets immediately
3152 SetNextThinkTimeASAP();
3153 }
3154
Think(SteamNetworkingMicroseconds usecNow)3155 void CSteamNetworkConnectionBase::Think( SteamNetworkingMicroseconds usecNow )
3156 {
3157 // NOTE: Lock has already been taken by ILockableThinker
3158
3159 // Safety check against leaving callbacks suppressed. If this fires, there's a good chance
3160 // we have already suppressed a callback that we should have posted, which is very bad
3161 AssertMsg( m_nSupressStateChangeCallbacks == 0, "[%s] m_nSupressStateChangeCallbacks left on!", GetDescription() );
3162 m_nSupressStateChangeCallbacks = 0;
3163
3164 // CheckConnectionStateAndSetNextThinkTime does all the work of examining the current state
3165 // and deciding what to do. But it should be safe to call at any time, whereas Think()
3166 // has a fixed contract: it should only be called by the thinker framework.
3167 CheckConnectionStateAndSetNextThinkTime( usecNow );
3168
3169 // Release lock
3170 m_pLock->unlock();
3171 }
3172
CheckConnectionStateOrScheduleWakeUp(SteamNetworkingMicroseconds usecNow)3173 void CSteamNetworkConnectionBase::CheckConnectionStateOrScheduleWakeUp( SteamNetworkingMicroseconds usecNow )
3174 {
3175 m_pLock->AssertHeldByCurrentThread();
3176
3177 // FIXME - try to take global lock, and if we can take it, then take action immediately
3178 SetNextThinkTimeASAP();
3179 }
3180
CheckConnectionStateAndSetNextThinkTime(SteamNetworkingMicroseconds usecNow)3181 void CSteamNetworkConnectionBase::CheckConnectionStateAndSetNextThinkTime( SteamNetworkingMicroseconds usecNow )
3182 {
3183 AssertLocksHeldByCurrentThread();
3184
3185 // Assume a default think interval just to make sure we check in periodically
3186 SteamNetworkingMicroseconds usecMinNextThinkTime = usecNow + k_nMillion;
3187
3188 // Use a macro so that if we assert, we'll get a real line number
3189 #define UpdateMinThinkTime(x) \
3190 { \
3191 /* assign into temporary in case x is an expression with side effects */ \
3192 SteamNetworkingMicroseconds usecNextThink = (x); \
3193 /* Scheduled think time must be in the future. If some code is setting a think */ \
3194 /* time for right now, then it should have just done it. */ \
3195 if ( usecNextThink <= usecNow ) { \
3196 AssertMsg1( false, "Trying to set next think time %lldusec in the past", (long long)( usecNow - usecMinNextThinkTime ) ); \
3197 usecNextThink = usecNow + 10*1000; \
3198 } \
3199 if ( usecNextThink < usecMinNextThinkTime ) \
3200 usecMinNextThinkTime = usecNextThink; \
3201 }
3202
3203 // Check our state
3204 switch ( m_eConnectionState )
3205 {
3206 case k_ESteamNetworkingConnectionState_Dead:
3207 case k_ESteamNetworkingConnectionState_None:
3208 default:
3209 // WAT
3210 Assert( false );
3211 return;
3212
3213 case k_ESteamNetworkingConnectionState_FinWait:
3214 {
3215 // Timeout?
3216 SteamNetworkingMicroseconds usecTimeout = m_usecWhenEnteredConnectionState + k_usecFinWaitTimeout;
3217 if ( usecNow >= usecTimeout )
3218 {
3219 ConnectionQueueDestroy();
3220 return;
3221 }
3222
3223 // It's not time yet, make sure we get our callback when it's time.
3224 EnsureMinThinkTime( usecTimeout );
3225 }
3226 return;
3227
3228 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
3229 case k_ESteamNetworkingConnectionState_ClosedByPeer:
3230 {
3231 // We don't send any data packets or keepalives in this state.
3232 // We're just waiting for the client API to close us. Let's check
3233 // in once after a pretty lengthy delay, and assert if we're still alive
3234 SteamNetworkingMicroseconds usecTimeout = m_usecWhenEnteredConnectionState + 20*k_nMillion;
3235 if ( usecNow >= usecTimeout )
3236 {
3237 SpewBug( "[%s] We are in state %d and have been waiting %.1fs to be cleaned up. Did you forget to call CloseConnection()?",
3238 GetDescription(), m_eConnectionState, ( usecNow - m_usecWhenEnteredConnectionState ) * 1e-6f );
3239 }
3240 else
3241 {
3242 SetNextThinkTime( usecTimeout );
3243 }
3244 }
3245 return;
3246
3247 case k_ESteamNetworkingConnectionState_FindingRoute:
3248 case k_ESteamNetworkingConnectionState_Connecting:
3249 {
3250
3251 // Timeout?
3252 SteamNetworkingMicroseconds usecTimeout = m_usecWhenEnteredConnectionState + (SteamNetworkingMicroseconds)m_connectionConfig.m_TimeoutInitial.Get()*1000;
3253 if ( usecNow >= usecTimeout )
3254 {
3255 // Check if the application just didn't ever respond, it's probably a bug.
3256 // We should squawk about this and let them know.
3257 if ( m_eConnectionState != k_ESteamNetworkingConnectionState_FindingRoute && m_bConnectionInitiatedRemotely )
3258 {
3259 // Discard this warning for messages sessions. It's part of the messages API design
3260 // that the app can ignore these connections if they do not want to accept them.
3261 if ( IsConnectionForMessagesSession() )
3262 {
3263 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_Timeout, "%s", "App did not respond to Messages session request in time, discarding." );
3264 }
3265 else
3266 {
3267 AssertMsg( false, "Application didn't accept or close incoming connection in a reasonable amount of time. This is probably a bug." );
3268 ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_Timeout, "%s", "App didn't accept or close incoming connection in time." );
3269 }
3270 }
3271 else
3272 {
3273 ConnectionTimedOut( usecNow );
3274 }
3275 AssertMsg2( GetState() == k_ESteamNetworkingConnectionState_ProblemDetectedLocally, "[%s] ConnectionTimedOut didn't do what it is supposed to, state=%d!", GetDescription(), (int)GetState() );
3276 return;
3277 }
3278
3279 // Make sure we wake up when timeout happens
3280 UpdateMinThinkTime( usecTimeout );
3281
3282 // FInding route?
3283 if ( m_eConnectionState == k_ESteamNetworkingConnectionState_FindingRoute )
3284 {
3285 UpdateMinThinkTime( ThinkConnection_FindingRoute( usecNow ) );
3286 }
3287 else if ( m_bConnectionInitiatedRemotely )
3288 {
3289 // We're waiting on the app to accept the connection. Nothing to do here.
3290 }
3291 else
3292 {
3293
3294 // Do we have all of our crypt stuff ready?
3295 if ( BThinkCryptoReady( usecNow ) )
3296 {
3297 // Send connect requests
3298 UpdateMinThinkTime( ThinkConnection_ClientConnecting( usecNow ) );
3299 }
3300 else
3301 {
3302 // Waiting on certs, etc. Make sure and check
3303 // back in periodically. Note that we we should be awoken
3304 // immediately when we get our cert. Is this necessary?
3305 // Might just be hiding a bug.
3306 UpdateMinThinkTime( usecNow + k_nMillion/20 );
3307 }
3308 }
3309 } break;
3310
3311 case k_ESteamNetworkingConnectionState_Linger:
3312
3313 // Have we sent everything we wanted to?
3314 if ( m_senderState.m_messagesQueued.empty() && m_senderState.m_unackedReliableMessages.empty() )
3315 {
3316 // Close the connection ASAP
3317 ConnectionState_FinWait();
3318 return;
3319 }
3320 // FALLTHROUGH
3321
3322 // |
3323 // | otherwise, fall through
3324 // V
3325 case k_ESteamNetworkingConnectionState_Connected:
3326 {
3327 if ( m_pTransport && m_pTransport->BCanSendEndToEndData() )
3328 {
3329 SteamNetworkingMicroseconds usecNextThinkSNP = SNP_ThinkSendState( usecNow );
3330
3331 // Set a pretty tight tolerance if SNP wants to wake up at a certain time.
3332 UpdateMinThinkTime( usecNextThinkSNP );
3333 }
3334 else
3335 {
3336 UpdateMinThinkTime( usecNow + 20*1000 );
3337 }
3338 } break;
3339 }
3340
3341 // Update stats
3342 m_statsEndToEnd.Think( usecNow );
3343 UpdateMTUFromConfig();
3344
3345 // Check for sending keepalives or probing a connection that appears to be timing out
3346 if ( BStateIsConnectedForWirePurposes() )
3347 {
3348 Assert( m_statsEndToEnd.m_usecTimeLastRecv > 0 ); // How did we get connected without receiving anything end-to-end?
3349 AssertMsg2( !m_statsEndToEnd.IsPassive(), "[%s] stats passive, but in state %d?", GetDescription(), (int)GetState() );
3350
3351 // Not able to send end-to-end data?
3352 bool bCanSendEndToEnd = m_pTransport && m_pTransport->BCanSendEndToEndData();
3353
3354 // Mark us as "timing out" if we are not able to send end-to-end data
3355 if ( !bCanSendEndToEnd && m_statsEndToEnd.m_usecWhenTimeoutStarted == 0 )
3356 m_statsEndToEnd.m_usecWhenTimeoutStarted = usecNow;
3357
3358 // Are we timing out?
3359 if ( m_statsEndToEnd.m_usecWhenTimeoutStarted > 0 )
3360 {
3361
3362 // When will the timeout hit?
3363 SteamNetworkingMicroseconds usecEndToEndConnectionTimeout = std::max( m_statsEndToEnd.m_usecWhenTimeoutStarted, m_statsEndToEnd.m_usecTimeLastRecv ) + (SteamNetworkingMicroseconds)m_connectionConfig.m_TimeoutConnected.Get()*1000;
3364
3365 // Time to give up?
3366 if ( usecNow >= usecEndToEndConnectionTimeout )
3367 {
3368 if ( m_statsEndToEnd.m_nReplyTimeoutsSinceLastRecv >= 4 || !bCanSendEndToEnd )
3369 {
3370 if ( bCanSendEndToEnd )
3371 {
3372 Assert( m_statsEndToEnd.m_usecWhenTimeoutStarted > 0 );
3373 SpewMsg( "[%s] Timed out. %.1fms since last recv, %.1fms since timeout started, %d consecutive failures\n",
3374 GetDescription(), ( usecNow - m_statsEndToEnd.m_usecTimeLastRecv ) * 1e-3, ( usecNow - m_statsEndToEnd.m_usecWhenTimeoutStarted ) * 1e-3, m_statsEndToEnd.m_nReplyTimeoutsSinceLastRecv );
3375 }
3376 else
3377 {
3378 SpewMsg( "[%s] Timed out. Cannot send end-to-end. %.1fms since last recv, %d consecutive failures\n",
3379 GetDescription(), ( usecNow - m_statsEndToEnd.m_usecTimeLastRecv ) * 1e-3, m_statsEndToEnd.m_nReplyTimeoutsSinceLastRecv );
3380 }
3381
3382 // Save state for assert
3383 #ifdef DBGFLAG_ASSERT
3384 ESteamNetworkingConnectionState eOldState = GetState();
3385 #endif
3386
3387 // Check for connection-type-specific handling
3388 ConnectionTimedOut( usecNow );
3389
3390 // Make sure that worked
3391 AssertMsg3(
3392 GetState() == k_ESteamNetworkingConnectionState_ProblemDetectedLocally
3393 || ( eOldState == k_ESteamNetworkingConnectionState_Linger && GetState() == k_ESteamNetworkingConnectionState_FinWait ),
3394 "[%s] ConnectionTimedOut didn't do what it is supposed to! (%d -> %d)", GetDescription(), (int)eOldState, (int)GetState() );
3395 return;
3396 }
3397 }
3398
3399 // Make sure we are waking up regularly to check in while this is going on
3400 UpdateMinThinkTime( usecNow + 50*1000 );
3401 }
3402
3403 // Check for sending keepalives and stats
3404 if ( bCanSendEndToEnd )
3405 {
3406
3407 // Urgent keepalive because we are timing out?
3408 SteamNetworkingMicroseconds usecStatsNextThinkTime = k_nThinkTime_Never;
3409 EStatsReplyRequest eReplyRequested;
3410 const char *pszStatsReason = m_statsEndToEnd.GetSendReasonOrUpdateNextThinkTime( usecNow, eReplyRequested, usecStatsNextThinkTime );
3411 if ( pszStatsReason )
3412 {
3413
3414 // Spew if we're dropping replies
3415 if ( m_statsEndToEnd.m_nReplyTimeoutsSinceLastRecv == 1 )
3416 SpewVerbose( "[%s] Reply timeout, last recv %.1fms ago. Sending keepalive.\n", GetDescription(), ( usecNow - m_statsEndToEnd.m_usecTimeLastRecv ) * 1e-3 );
3417 else if ( m_statsEndToEnd.m_nReplyTimeoutsSinceLastRecv > 0 )
3418 SpewMsg( "[%s] %d reply timeouts, last recv %.1fms ago. Sending keepalive.\n", GetDescription(), m_statsEndToEnd.m_nReplyTimeoutsSinceLastRecv, ( usecNow - m_statsEndToEnd.m_usecTimeLastRecv ) * 1e-3 );
3419
3420 // Send it
3421 m_pTransport->SendEndToEndStatsMsg( eReplyRequested, usecNow, pszStatsReason );
3422
3423 // Re-calculate next think time
3424 usecStatsNextThinkTime = k_nThinkTime_Never;
3425 const char *pszStatsReason2 = m_statsEndToEnd.GetSendReasonOrUpdateNextThinkTime( usecNow, eReplyRequested, usecStatsNextThinkTime );
3426 AssertMsg1( pszStatsReason2 == nullptr && usecStatsNextThinkTime > usecNow, "Stats sending didn't clear stats need to send reason %s!", pszStatsReason2 ? pszStatsReason2 : "??" );
3427 }
3428
3429 // Make sure we are scheduled to wake up the next time we need to take action
3430 UpdateMinThinkTime( usecStatsNextThinkTime );
3431 }
3432 }
3433
3434 // Hook for derived class to do its connection-type-specific stuff
3435 ThinkConnection( usecNow );
3436
3437 // Check for sending diagnostics periodically
3438 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_DIAGNOSTICSUI
3439 if ( m_usecWhenNextDiagnosticsUpdate <= usecNow )
3440 {
3441 m_pSteamNetworkingSocketsInterface->m_pSteamNetworkingUtils->PostConnectionStateUpdateForDiagnosticsUI( CollapseConnectionStateToAPIState( GetState() ), this, usecNow );
3442 Assert( m_usecWhenNextDiagnosticsUpdate > usecNow );
3443 }
3444 UpdateMinThinkTime( m_usecWhenNextDiagnosticsUpdate );
3445 #endif
3446
3447 // Schedule next time to think, if derived class didn't request an earlier
3448 // wakeup call.
3449 EnsureMinThinkTime( usecMinNextThinkTime );
3450
3451 #undef UpdateMinThinkTime
3452 }
3453
ThinkConnection(SteamNetworkingMicroseconds usecNow)3454 void CSteamNetworkConnectionBase::ThinkConnection( SteamNetworkingMicroseconds usecNow )
3455 {
3456 }
3457
ProcessSNPPing(int msPing,RecvPacketContext_t & ctx)3458 void CSteamNetworkConnectionBase::ProcessSNPPing( int msPing, RecvPacketContext_t &ctx )
3459 {
3460 m_statsEndToEnd.m_ping.ReceivedPing( msPing, ctx.m_usecNow );
3461 }
3462
ThinkConnection_FindingRoute(SteamNetworkingMicroseconds usecNow)3463 SteamNetworkingMicroseconds CSteamNetworkConnectionBase::ThinkConnection_FindingRoute( SteamNetworkingMicroseconds usecNow )
3464 {
3465 return k_nThinkTime_Never;
3466 }
3467
ThinkConnection_ClientConnecting(SteamNetworkingMicroseconds usecNow)3468 SteamNetworkingMicroseconds CSteamNetworkConnectionBase::ThinkConnection_ClientConnecting( SteamNetworkingMicroseconds usecNow )
3469 {
3470 Assert( !m_bConnectionInitiatedRemotely );
3471
3472 // Default behaviour for client periodically sending connect requests
3473
3474 // Ask transport if it's ready
3475 if ( !m_pTransport || !m_pTransport->BCanSendEndToEndConnectRequest() )
3476 return usecNow + k_nMillion/20; // Nope, check back in just a bit.
3477
3478 // When to send next retry.
3479 SteamNetworkingMicroseconds usecRetry = m_usecWhenSentConnectRequest + k_usecConnectRetryInterval;
3480 if ( usecNow < usecRetry )
3481 return usecRetry; // attempt already in flight. Wait until it's time to retry
3482
3483 // Send a request
3484 m_pTransport->SendEndToEndConnectRequest( usecNow );
3485 m_usecWhenSentConnectRequest = usecNow;
3486
3487 // And wakeup when it will be time to retry
3488 return m_usecWhenSentConnectRequest + k_usecConnectRetryInterval;
3489 }
3490
ConnectionTimedOut(SteamNetworkingMicroseconds usecNow)3491 void CSteamNetworkConnectionBase::ConnectionTimedOut( SteamNetworkingMicroseconds usecNow )
3492 {
3493 AssertLocksHeldByCurrentThread();
3494
3495 ESteamNetConnectionEnd nReasonCode;
3496 ConnectionEndDebugMsg msg;
3497
3498 // Set some generic defaults.
3499 nReasonCode = k_ESteamNetConnectionEnd_Misc_Timeout;
3500 switch ( GetState() )
3501 {
3502 case k_ESteamNetworkingConnectionState_Connecting:
3503 // Should use this more specific reason code at somepoint, when I add an API to get localized error messages
3504 //if ( !m_bConnectionInitiatedRemotely && m_statsEndToEnd.m_usecTimeLastRecv == 0 )
3505 //{
3506 // nReasonCode = k_ESteamNetConnectionEnd_Misc_ServerNeverReplied;
3507 // V_strcpy_safe( msg, "" );
3508 //}
3509 //else
3510 //{
3511 V_strcpy_safe( msg, "Timed out attempting to connect" );
3512 //}
3513 break;
3514
3515 case k_ESteamNetworkingConnectionState_FindingRoute:
3516 nReasonCode = k_ESteamNetConnectionEnd_Misc_P2P_Rendezvous;
3517 V_strcpy_safe( msg, "Timed out attempting to negotiate rendezvous" );
3518 break;
3519
3520 default:
3521 V_strcpy_safe( msg, "Connection dropped" );
3522 break;
3523 }
3524
3525 // Check if connection has a more enlightened understanding of what's wrong
3526 ConnectionGuessTimeoutReason( nReasonCode, msg, usecNow );
3527
3528 // Switch connection state
3529 ConnectionState_ProblemDetectedLocally( nReasonCode, "%s", msg );
3530 }
3531
ConnectionGuessTimeoutReason(ESteamNetConnectionEnd & nReasonCode,ConnectionEndDebugMsg & msg,SteamNetworkingMicroseconds usecNow)3532 void CSteamNetworkConnectionBase::ConnectionGuessTimeoutReason( ESteamNetConnectionEnd &nReasonCode, ConnectionEndDebugMsg &msg, SteamNetworkingMicroseconds usecNow )
3533 {
3534 // Base class, just delegate to active transport
3535 if ( m_pTransport )
3536 m_pTransport->TransportGuessTimeoutReason( nReasonCode, msg, usecNow );
3537 }
3538
TransportGuessTimeoutReason(ESteamNetConnectionEnd & nReasonCode,ConnectionEndDebugMsg & msg,SteamNetworkingMicroseconds usecNow)3539 void CConnectionTransport::TransportGuessTimeoutReason( ESteamNetConnectionEnd &nReasonCode, ConnectionEndDebugMsg &msg, SteamNetworkingMicroseconds usecNow )
3540 {
3541 // No enlightenments at base class
3542 }
3543
UpdateSpeeds(int nTXSpeed,int nRXSpeed)3544 void CSteamNetworkConnectionBase::UpdateSpeeds( int nTXSpeed, int nRXSpeed )
3545 {
3546 m_statsEndToEnd.UpdateSpeeds( nTXSpeed, nRXSpeed );
3547 }
3548
UpdateMTUFromConfig()3549 void CSteamNetworkConnectionBase::UpdateMTUFromConfig()
3550 {
3551 int newMTUPacketSize = m_connectionConfig.m_MTU_PacketSize.Get();
3552 if ( newMTUPacketSize == m_cbMTUPacketSize )
3553 return;
3554
3555 // Shrinking MTU?
3556 if ( newMTUPacketSize < m_cbMTUPacketSize )
3557 {
3558 // We cannot do this while we have any reliable segments in flight!
3559 // To keep things simple, the retries are always the original ranges,
3560 // we never have our retries chop up the space differently than
3561 // the original send
3562 if ( !m_senderState.m_listReadyRetryReliableRange.empty() || !m_senderState.m_listInFlightReliableRange.empty() )
3563 return;
3564 }
3565
3566 m_cbMTUPacketSize = m_connectionConfig.m_MTU_PacketSize.Get();
3567 m_cbMaxPlaintextPayloadSend = m_cbMTUPacketSize - ( k_cbSteamNetworkingSocketsMaxUDPMsgLen - k_cbSteamNetworkingSocketsMaxPlaintextPayloadSend );
3568 m_cbMaxMessageNoFragment = m_cbMaxPlaintextPayloadSend - k_cbSteamNetworkingSocketsNoFragmentHeaderReserve;
3569
3570 // Max size of a reliable segment. This is designed such that a reliable
3571 // message of size k_cbSteamNetworkingSocketsMaxMessageNoFragment
3572 // won't get fragmented, except perhaps in an exceedingly degenerate
3573 // case. (Even in this case, the protocol will function properly, it
3574 // will just potentially fragment the message.) We shouldn't make any
3575 // hard promises in this department.
3576 //
3577 // 1 byte - message header
3578 // 3 bytes - varint encode msgnum gap between previous reliable message. (Gap could be greater, but this would be really unusual.)
3579 // 1 byte - size remainder bytes (assuming message is k_cbSteamNetworkingSocketsMaxMessageNoFragment, we only need a single size overflow byte)
3580 m_cbMaxReliableMessageSegment = m_cbMaxMessageNoFragment + 5;
3581 }
3582
AsSteamNetworkConnectionP2P()3583 CSteamNetworkConnectionP2P *CSteamNetworkConnectionBase::AsSteamNetworkConnectionP2P()
3584 {
3585 return nullptr;
3586 }
3587
3588 /////////////////////////////////////////////////////////////////////////////
3589 //
3590 // CSteamNetworkConnectionPipe
3591 //
3592 /////////////////////////////////////////////////////////////////////////////
3593
APICreateSocketPair(CSteamNetworkingSockets * pSteamNetworkingSocketsInterface,CSteamNetworkConnectionPipe * pConn[2],const SteamNetworkingIdentity pIdentity[2])3594 bool CSteamNetworkConnectionPipe::APICreateSocketPair( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface, CSteamNetworkConnectionPipe *pConn[2], const SteamNetworkingIdentity pIdentity[2] )
3595 {
3596 SteamDatagramErrMsg errMsg;
3597 SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
3598 ConnectionScopeLock scopeLock[2];
3599
3600 pConn[1] = new CSteamNetworkConnectionPipe( pSteamNetworkingSocketsInterface, pIdentity[0], scopeLock[0] );
3601 pConn[0] = new CSteamNetworkConnectionPipe( pSteamNetworkingSocketsInterface, pIdentity[1], scopeLock[1] );
3602 if ( !pConn[0] || !pConn[1] )
3603 {
3604 failed:
3605 pConn[0]->ConnectionQueueDestroy(); pConn[0] = nullptr;
3606 pConn[1]->ConnectionQueueDestroy(); pConn[1] = nullptr;
3607 return false;
3608 }
3609
3610 pConn[0]->m_pPartner = pConn[1];
3611 pConn[1]->m_pPartner = pConn[0];
3612
3613 // Don't post any state changes for these transitions. We just want to immediately start in the
3614 // connected state
3615 ++pConn[0]->m_nSupressStateChangeCallbacks;
3616 ++pConn[1]->m_nSupressStateChangeCallbacks;
3617
3618 // Do generic base class initialization
3619 for ( int i = 0 ; i < 2 ; ++i )
3620 {
3621 CSteamNetworkConnectionPipe *p = pConn[i];
3622 CSteamNetworkConnectionPipe *q = pConn[1-i];
3623 if ( !p->BInitConnection( usecNow, 0, nullptr, errMsg ) )
3624 {
3625 AssertMsg1( false, "CSteamNetworkConnectionPipe::BInitConnection failed. %s", errMsg );
3626 goto failed;
3627 }
3628 p->m_identityRemote = q->m_identityLocal;
3629 p->m_unConnectionIDRemote = q->m_unConnectionIDLocal;
3630 }
3631
3632 // Exchange some dummy "connect" packets so that all of our internal variables
3633 // (and ping) look as realistic as possible
3634 pConn[0]->FakeSendStats( usecNow, 0 );
3635 pConn[1]->FakeSendStats( usecNow, 0 );
3636
3637 // Tie the connections to each other, and mark them as connected
3638 for ( int i = 0 ; i < 2 ; ++i )
3639 {
3640 CSteamNetworkConnectionPipe *p = pConn[i];
3641 CSteamNetworkConnectionPipe *q = pConn[1-i];
3642 p->m_identityRemote = q->m_identityLocal;
3643 p->m_unConnectionIDRemote = q->m_unConnectionIDLocal;
3644 if ( !p->BRecvCryptoHandshake( q->m_msgSignedCertLocal, q->m_msgSignedCryptLocal, i==0 ) )
3645 {
3646 AssertMsg( false, "BRecvCryptoHandshake failed creating loopback pipe socket pair" );
3647 goto failed;
3648 }
3649 if ( !p->BConnectionState_Connecting( usecNow, errMsg ) )
3650 {
3651 AssertMsg( false, "BConnectionState_Connecting failed creating loopback pipe socket pair. %s", errMsg );
3652 goto failed;
3653 }
3654 p->ConnectionState_Connected( usecNow );
3655 }
3656
3657 // Any further state changes are legit
3658 pConn[0]->m_nSupressStateChangeCallbacks = 0;
3659 pConn[1]->m_nSupressStateChangeCallbacks = 0;
3660 return true;
3661 }
3662
CreateLoopbackConnection(CSteamNetworkingSockets * pClientInstance,int nOptions,const SteamNetworkingConfigValue_t * pOptions,CSteamNetworkListenSocketBase * pListenSocket,SteamNetworkingErrMsg & errMsg,ConnectionScopeLock & scopeLock)3663 CSteamNetworkConnectionPipe *CSteamNetworkConnectionPipe::CreateLoopbackConnection(
3664 CSteamNetworkingSockets *pClientInstance, int nOptions, const SteamNetworkingConfigValue_t *pOptions,
3665 CSteamNetworkListenSocketBase *pListenSocket,
3666 SteamNetworkingErrMsg &errMsg,
3667 ConnectionScopeLock &scopeLock
3668 ) {
3669 SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
3670
3671 CSteamNetworkingSockets *pServerInstance = pListenSocket->m_pSteamNetworkingSocketsInterface;
3672
3673 ConnectionScopeLock serverScopeLock;
3674 CSteamNetworkConnectionPipe *pClient = new CSteamNetworkConnectionPipe( pClientInstance, pClientInstance->InternalGetIdentity(), scopeLock );
3675 CSteamNetworkConnectionPipe *pServer = new CSteamNetworkConnectionPipe( pServerInstance, pServerInstance->InternalGetIdentity(), serverScopeLock );
3676 if ( !pClient || !pServer )
3677 {
3678 V_strcpy_safe( errMsg, "new CSteamNetworkConnectionPipe failed" );
3679 failed:
3680 if ( pClient )
3681 pClient->ConnectionQueueDestroy();
3682 if ( pServer )
3683 pServer->ConnectionQueueDestroy();
3684 return nullptr;
3685 }
3686
3687 // Link em up
3688 pClient->m_pPartner = pServer;
3689 pServer->m_pPartner = pClient;
3690
3691 // Initialize client connection. This triggers a state transition callback
3692 // to the "connecting" state
3693 if ( !pClient->BInitConnection( usecNow, nOptions, pOptions, errMsg ) )
3694 goto failed;
3695
3696 // Server receives the connection and starts "accepting" it
3697 if ( !pServer->BBeginAccept( pListenSocket, usecNow, errMsg ) )
3698 goto failed;
3699
3700 // Client sends a "connect" packet
3701 if ( !pClient->BConnectionState_Connecting( usecNow, errMsg ) )
3702 goto failed;
3703 Assert( pServer->m_statsEndToEnd.m_nMaxRecvPktNum == 1 );
3704 pClient->m_statsEndToEnd.m_nNextSendSequenceNumber = pServer->m_statsEndToEnd.m_nMaxRecvPktNum+1;
3705 pClient->FakeSendStats( usecNow, 0 );
3706
3707 // Now we wait for the app to accept the connection
3708 return pClient;
3709 }
3710
3711 // All pipe connections share the same lock!
3712 static ConnectionLock s_sharedPipeLock;
3713
CSteamNetworkConnectionPipe(CSteamNetworkingSockets * pSteamNetworkingSocketsInterface,const SteamNetworkingIdentity & identity,ConnectionScopeLock & scopeLock)3714 CSteamNetworkConnectionPipe::CSteamNetworkConnectionPipe( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface, const SteamNetworkingIdentity &identity, ConnectionScopeLock &scopeLock )
3715 : CSteamNetworkConnectionBase( pSteamNetworkingSocketsInterface, scopeLock )
3716 , CConnectionTransport( *static_cast<CSteamNetworkConnectionBase*>( this ) ) // connection and transport object are the same
3717 , m_pPartner( nullptr )
3718 {
3719 m_identityLocal = identity;
3720 m_pTransport = this;
3721
3722 // All pipe connections use a shared, global lock.
3723 // We could optimize this so that only pairs of connections share
3724 // a lock, but this is easy and we don't expect the lock to be
3725 // held for very long at a time
3726 scopeLock.Unlock();
3727 m_pLock = &s_sharedPipeLock;
3728 scopeLock.Lock( *this );
3729
3730 // Encryption is not used for pipe connections.
3731 // This is not strictly necessary, since we never even send packets or
3732 // touch payload bytes at all, we just shift some pointers around.
3733 // But it's nice to make it official
3734 m_connectionConfig.m_Unencrypted.Set( 3 );
3735
3736 // Slam in a really large SNP rate so that we are never rate limited
3737 int nRate = 0x10000000;
3738 m_connectionConfig.m_SendRateMin.Set( nRate );
3739 m_connectionConfig.m_SendRateMax.Set( nRate );
3740 }
3741
~CSteamNetworkConnectionPipe()3742 CSteamNetworkConnectionPipe::~CSteamNetworkConnectionPipe()
3743 {
3744 Assert( !m_pPartner );
3745 }
3746
GetConnectionTypeDescription(ConnectionTypeDescription_t & szDescription) const3747 void CSteamNetworkConnectionPipe::GetConnectionTypeDescription( ConnectionTypeDescription_t &szDescription ) const
3748 {
3749 V_strcpy_safe( szDescription, "pipe" );
3750 }
3751
InitConnectionCrypto(SteamNetworkingMicroseconds usecNow)3752 void CSteamNetworkConnectionPipe::InitConnectionCrypto( SteamNetworkingMicroseconds usecNow )
3753 {
3754 // Always use unsigned cert, since we won't be doing any real crypto anyway
3755 SetLocalCertUnsigned();
3756 }
3757
AllowRemoteUnsignedCert()3758 EUnsignedCert CSteamNetworkConnectionPipe::AllowRemoteUnsignedCert()
3759 {
3760 // It's definitely us, and we trust ourselves, right?
3761 return k_EUnsignedCert_Allow;
3762 }
3763
AllowLocalUnsignedCert()3764 EUnsignedCert CSteamNetworkConnectionPipe::AllowLocalUnsignedCert()
3765 {
3766 // It's definitely us, and we trust ourselves, right? Don't even try to get a cert
3767 return k_EUnsignedCert_Allow;
3768 }
3769
_APISendMessageToConnection(CSteamNetworkingMessage * pMsg,SteamNetworkingMicroseconds usecNow,bool * pbThinkImmediately)3770 int64 CSteamNetworkConnectionPipe::_APISendMessageToConnection( CSteamNetworkingMessage *pMsg, SteamNetworkingMicroseconds usecNow, bool *pbThinkImmediately )
3771 {
3772 NOTE_UNUSED( pbThinkImmediately );
3773 if ( !m_pPartner )
3774 {
3775 // Caller should have checked the connection at a higher level, so this is a bug
3776 AssertMsg( false, "No partner pipe?" );
3777 pMsg->Release();
3778 return -k_EResultFail;
3779 }
3780
3781 // We have partner pipes use the same lock, to keep this code simple
3782 Assert( m_pPartner->m_pLock == m_pLock );
3783 m_pLock->AssertHeldByCurrentThread();
3784
3785 // Fake a bunch of stats
3786 FakeSendStats( usecNow, pMsg->m_cbSize );
3787
3788 // Set fields to their values applicable on the receiving side
3789 // NOTE: This assumes that we can muck with the structure,
3790 // and that the caller won't need to look at the original
3791 // object any more.
3792 int nMsgNum = ++m_senderState.m_nLastSentMsgNum;
3793 pMsg->m_nMessageNumber = nMsgNum;
3794 pMsg->m_conn = m_pPartner->m_hConnectionSelf;
3795 pMsg->m_identityPeer = m_pPartner->m_identityRemote;
3796 pMsg->m_nConnUserData = m_pPartner->GetUserData();
3797 pMsg->m_usecTimeReceived = usecNow;
3798
3799 // Pass directly to our partner
3800 m_pPartner->ReceivedMessage( pMsg );
3801
3802 return nMsgNum;
3803 }
3804
BBeginAccept(CSteamNetworkListenSocketBase * pListenSocket,SteamNetworkingMicroseconds usecNow,SteamDatagramErrMsg & errMsg)3805 bool CSteamNetworkConnectionPipe::BBeginAccept( CSteamNetworkListenSocketBase *pListenSocket, SteamNetworkingMicroseconds usecNow, SteamDatagramErrMsg &errMsg )
3806 {
3807 // We have partner pipes use the same lock, to keep this code simple
3808 Assert( m_pPartner->m_pLock == m_pLock );
3809 m_pLock->AssertHeldByCurrentThread();
3810
3811 // Ordinary connections usually learn the client identity and connection ID at this point
3812 m_identityRemote = m_pPartner->m_identityLocal;
3813 m_unConnectionIDRemote = m_pPartner->m_unConnectionIDLocal;
3814
3815 // Act like we came in on this listen socket
3816 if ( !pListenSocket->BAddChildConnection( this, errMsg ) )
3817 return false;
3818
3819 // Assign connection ID, init crypto, transition to "connecting" state.
3820 if ( !BInitConnection( usecNow, 0, nullptr, errMsg ) )
3821 return false;
3822
3823 // Receive the crypto info that is in the client's
3824 if ( !BRecvCryptoHandshake( m_pPartner->m_msgSignedCertLocal, m_pPartner->m_msgSignedCryptLocal, true ) )
3825 {
3826 Assert( GetState() == k_ESteamNetworkingConnectionState_ProblemDetectedLocally );
3827 V_sprintf_safe( errMsg, "Failed crypto init. %s", m_szEndDebug );
3828 return false;
3829 }
3830
3831 // Transition to connecting state
3832 return BConnectionState_Connecting( usecNow, errMsg );
3833 }
3834
AcceptConnection(SteamNetworkingMicroseconds usecNow)3835 EResult CSteamNetworkConnectionPipe::AcceptConnection( SteamNetworkingMicroseconds usecNow )
3836 {
3837 if ( !m_pPartner )
3838 {
3839 Assert( false );
3840 return k_EResultFail;
3841 }
3842
3843 // We have partner pipes use the same lock, to keep this code simple
3844 CSteamNetworkConnectionBase::AssertLocksHeldByCurrentThread();
3845 Assert( m_pPartner->m_pLock == m_pLock );
3846
3847 // Mark server side connection as connected
3848 ConnectionState_Connected( usecNow );
3849
3850 // "Send connect OK" to partner, and he is connected
3851 FakeSendStats( usecNow, 0 );
3852
3853 // Partner "receives" ConnectOK
3854 m_pPartner->m_identityRemote = m_identityLocal;
3855 m_pPartner->m_unConnectionIDRemote = m_unConnectionIDLocal;
3856 if ( !m_pPartner->BRecvCryptoHandshake( m_msgSignedCertLocal, m_msgSignedCryptLocal, false ) )
3857 return k_EResultHandshakeFailed;
3858 m_pPartner->ConnectionState_Connected( usecNow );
3859
3860 return k_EResultOK;
3861 }
3862
FakeSendStats(SteamNetworkingMicroseconds usecNow,int cbPktSize)3863 void CSteamNetworkConnectionPipe::FakeSendStats( SteamNetworkingMicroseconds usecNow, int cbPktSize )
3864 {
3865 if ( !m_pPartner )
3866 return;
3867
3868 // We have partner pipes use the same lock, to keep this code simple.
3869 // Note that we might not hold the global lock!
3870 Assert( m_pPartner->m_pLock == m_pLock );
3871 m_pLock->AssertHeldByCurrentThread();
3872
3873 // Get the next packet number we would have sent
3874 uint16 nSeqNum = m_statsEndToEnd.ConsumeSendPacketNumberAndGetWireFmt( usecNow );
3875
3876 // And the peer receiving it immediately. And assume every packet represents
3877 // a ping measurement.
3878 int64 nPktNum = m_pPartner->m_statsEndToEnd.ExpandWirePacketNumberAndCheck( nSeqNum );
3879 Assert( nPktNum+1 == m_statsEndToEnd.m_nNextSendSequenceNumber );
3880 m_pPartner->m_statsEndToEnd.TrackProcessSequencedPacket( nPktNum, usecNow, -1 );
3881 m_pPartner->m_statsEndToEnd.TrackRecvPacket( cbPktSize, usecNow );
3882 m_pPartner->m_statsEndToEnd.m_ping.ReceivedPing( 0, usecNow );
3883
3884 // Fake sending stats
3885 m_statsEndToEnd.TrackSentPacket( cbPktSize );
3886 }
3887
SendEndToEndStatsMsg(EStatsReplyRequest eRequest,SteamNetworkingMicroseconds usecNow,const char * pszReason)3888 void CSteamNetworkConnectionPipe::SendEndToEndStatsMsg( EStatsReplyRequest eRequest, SteamNetworkingMicroseconds usecNow, const char *pszReason )
3889 {
3890 NOTE_UNUSED( eRequest );
3891 NOTE_UNUSED( pszReason );
3892 CSteamNetworkConnectionBase::AssertLocksHeldByCurrentThread();
3893
3894 if ( !m_pPartner )
3895 {
3896 Assert( false );
3897 return;
3898 }
3899
3900 // Fake sending us a ping request
3901 m_statsEndToEnd.TrackSentPingRequest( usecNow, false );
3902 FakeSendStats( usecNow, 0 );
3903
3904 // Fake partner receiving it
3905 m_pPartner->m_statsEndToEnd.PeerAckedLifetime( usecNow );
3906 m_pPartner->m_statsEndToEnd.PeerAckedInstantaneous( usecNow );
3907
3908 // ...and sending us a reply immediately
3909 m_pPartner->FakeSendStats( usecNow, 0 );
3910
3911 // ... and us receiving it immediately
3912 m_statsEndToEnd.PeerAckedLifetime( usecNow );
3913 m_statsEndToEnd.PeerAckedInstantaneous( usecNow );
3914 }
3915
BCanSendEndToEndConnectRequest() const3916 bool CSteamNetworkConnectionPipe::BCanSendEndToEndConnectRequest() const
3917 {
3918 // We should only ask this question if we still have a chance of connecting.
3919 // Once we detach from partner, we should switch the connection state, and
3920 // the base class state machine should never ask this question of us
3921 Assert( m_pPartner );
3922 return m_pPartner != nullptr;
3923 }
3924
BCanSendEndToEndData() const3925 bool CSteamNetworkConnectionPipe::BCanSendEndToEndData() const
3926 {
3927 // We should only ask this question if we still have a chance of connecting.
3928 // Once we detach from partner, we should switch the connection state, and
3929 // the base class state machine should never ask this question of us
3930 Assert( m_pPartner );
3931 return m_pPartner != nullptr;
3932 }
3933
SendEndToEndConnectRequest(SteamNetworkingMicroseconds usecNow)3934 void CSteamNetworkConnectionPipe::SendEndToEndConnectRequest( SteamNetworkingMicroseconds usecNow )
3935 {
3936 // Send a "packet"
3937 FakeSendStats( usecNow, 0 );
3938 }
3939
SendDataPacket(SteamNetworkingMicroseconds usecNow)3940 bool CSteamNetworkConnectionPipe::SendDataPacket( SteamNetworkingMicroseconds usecNow )
3941 {
3942 AssertMsg( false, "CSteamNetworkConnectionPipe connections shouldn't try to send 'packets'!" );
3943 return false;
3944 }
3945
SendEncryptedDataChunk(const void * pChunk,int cbChunk,SendPacketContext_t & ctx)3946 int CSteamNetworkConnectionPipe::SendEncryptedDataChunk( const void *pChunk, int cbChunk, SendPacketContext_t &ctx )
3947 {
3948 AssertMsg( false, "CSteamNetworkConnectionPipe connections shouldn't try to send 'packets'!" );
3949 return -1;
3950 }
3951
TransportPopulateConnectionInfo(SteamNetConnectionInfo_t & info) const3952 void CSteamNetworkConnectionPipe::TransportPopulateConnectionInfo( SteamNetConnectionInfo_t &info ) const
3953 {
3954 CConnectionTransport::TransportPopulateConnectionInfo( info );
3955 info.m_nFlags |= k_nSteamNetworkConnectionInfoFlags_LoopbackBuffers | k_nSteamNetworkConnectionInfoFlags_Fast;
3956
3957 // Since we're using loopback buffers, the security flags can't really apply.
3958 // Make sure they are turned off
3959 info.m_nFlags &= ~k_nSteamNetworkConnectionInfoFlags_Unauthenticated;
3960 info.m_nFlags &= ~k_nSteamNetworkConnectionInfoFlags_Unencrypted;
3961 }
3962
ConnectionStateChanged(ESteamNetworkingConnectionState eOldState)3963 void CSteamNetworkConnectionPipe::ConnectionStateChanged( ESteamNetworkingConnectionState eOldState )
3964 {
3965 CSteamNetworkConnectionBase::ConnectionStateChanged( eOldState );
3966
3967 switch ( GetState() )
3968 {
3969 case k_ESteamNetworkingConnectionState_FindingRoute:
3970 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally: // What local "problem" could we have detected??
3971 default:
3972 AssertMsg1( false, "Invalid state %d", GetState() );
3973 // FALLTHROUGH
3974 case k_ESteamNetworkingConnectionState_None:
3975 case k_ESteamNetworkingConnectionState_Dead:
3976 case k_ESteamNetworkingConnectionState_FinWait:
3977 case k_ESteamNetworkingConnectionState_Linger:
3978 if ( m_pPartner )
3979 {
3980 // We have partner pipes use the same lock, to keep this code simple.
3981 // Note that we might not hold the global lock!
3982 Assert( m_pPartner->m_pLock == m_pLock );
3983
3984 CSteamNetworkConnectionPipe *pPartner = m_pPartner;
3985 m_pPartner = nullptr; // clear pointer now, to prevent recursion
3986 pPartner->ConnectionState_ClosedByPeer( m_eEndReason, m_szEndDebug );
3987 }
3988 break;
3989
3990 case k_ESteamNetworkingConnectionState_Connecting:
3991 case k_ESteamNetworkingConnectionState_Connected:
3992 Assert( m_pPartner );
3993 break;
3994
3995 case k_ESteamNetworkingConnectionState_ClosedByPeer:
3996
3997 // If we have a partner, they should be the ones initiating this.
3998 // (In the code directly above.)
3999 if ( m_pPartner )
4000 {
4001
4002 // We have partner pipes use the same lock, to keep this code simple.
4003 // Note that we might not hold the global lock!
4004 Assert( m_pPartner->m_pLock == m_pLock );
4005
4006 Assert( CollapseConnectionStateToAPIState( m_pPartner->GetState() ) == k_ESteamNetworkingConnectionState_None );
4007 Assert( m_pPartner->m_pPartner == nullptr );
4008 m_pPartner = nullptr;
4009 }
4010 break;
4011 }
4012 }
4013
DestroyTransport()4014 void CSteamNetworkConnectionPipe::DestroyTransport()
4015 {
4016 // Using the same object for connection and transport
4017 TransportFreeResources();
4018 m_pTransport = nullptr;
4019 }
4020
4021 } // namespace SteamNetworkingSocketsLib
4022