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