1 //====== Copyright Valve Corporation, All rights reserved. ====================
2
3 #include "csteamnetworkingsockets.h"
4 #include "steamnetworkingsockets_lowlevel.h"
5 #include "steamnetworkingsockets_connections.h"
6 #include "steamnetworkingsockets_udp.h"
7 #include "../steamnetworkingsockets_certstore.h"
8 #include "crypto.h"
9
10 #ifdef STEAMNETWORKINGSOCKETS_STANDALONELIB
11 #include <steam/steamnetworkingsockets.h>
12 #endif
13
14 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_STEAMNETWORKINGMESSAGES
15 #include "csteamnetworkingmessages.h"
16 #endif
17
18 // Needed for the platform checks below
19 #if defined(__APPLE__)
20 #include "AvailabilityMacros.h"
21 #include "TargetConditionals.h"
22 #endif
23
24 // memdbgon must be the last include file in a .cpp file!!!
25 #include "tier0/memdbgon.h"
26
~ISteamNetworkingSockets()27 ISteamNetworkingSockets::~ISteamNetworkingSockets() {}
~ISteamNetworkingUtils()28 ISteamNetworkingUtils::~ISteamNetworkingUtils() {}
29
30 // Put everything in a namespace, so we don't violate the one definition rule
31 namespace SteamNetworkingSocketsLib {
32
33 /////////////////////////////////////////////////////////////////////////////
34 //
35 // Configuration Variables
36 //
37 /////////////////////////////////////////////////////////////////////////////
38
39 DEFINE_GLOBAL_CONFIGVAL( float, FakePacketLoss_Send, 0.0f, 0.0f, 100.0f );
40 DEFINE_GLOBAL_CONFIGVAL( float, FakePacketLoss_Recv, 0.0f, 0.0f, 100.0f );
41 DEFINE_GLOBAL_CONFIGVAL( int32, FakePacketLag_Send, 0, 0, 5000 );
42 DEFINE_GLOBAL_CONFIGVAL( int32, FakePacketLag_Recv, 0, 0, 5000 );
43 DEFINE_GLOBAL_CONFIGVAL( float, FakePacketReorder_Send, 0.0f, 0.0f, 100.0f );
44 DEFINE_GLOBAL_CONFIGVAL( float, FakePacketReorder_Recv, 0.0f, 0.0f, 100.0f );
45 DEFINE_GLOBAL_CONFIGVAL( int32, FakePacketReorder_Time, 15, 0, 5000 );
46 DEFINE_GLOBAL_CONFIGVAL( float, FakePacketDup_Send, 0.0f, 0.0f, 100.0f );
47 DEFINE_GLOBAL_CONFIGVAL( float, FakePacketDup_Recv, 0.0f, 0.0f, 100.0f );
48 DEFINE_GLOBAL_CONFIGVAL( int32, FakePacketDup_TimeMax, 10, 0, 5000 );
49 DEFINE_GLOBAL_CONFIGVAL( int32, PacketTraceMaxBytes, -1, -1, 99999 );
50 DEFINE_GLOBAL_CONFIGVAL( int32, FakeRateLimit_Send_Rate, 0, 0, 1024*1024*1024 );
51 DEFINE_GLOBAL_CONFIGVAL( int32, FakeRateLimit_Send_Burst, 16*1024, 0, 1024*1024 );
52 DEFINE_GLOBAL_CONFIGVAL( int32, FakeRateLimit_Recv_Rate, 0, 0, 1024*1024*1024 );
53 DEFINE_GLOBAL_CONFIGVAL( int32, FakeRateLimit_Recv_Burst, 16*1024, 0, 1024*1024 );
54
55 DEFINE_GLOBAL_CONFIGVAL( int32, EnumerateDevVars, 0, 0, 1 );
56
57 DEFINE_GLOBAL_CONFIGVAL( void *, Callback_AuthStatusChanged, nullptr );
58 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_STEAMNETWORKINGMESSAGES
59 DEFINE_GLOBAL_CONFIGVAL( void*, Callback_MessagesSessionRequest, nullptr );
60 DEFINE_GLOBAL_CONFIGVAL( void*, Callback_MessagesSessionFailed, nullptr );
61 #endif
62 DEFINE_GLOBAL_CONFIGVAL( void *, Callback_CreateConnectionSignaling, nullptr );
63
64 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, TimeoutInitial, 10000, 0, INT32_MAX );
65 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, TimeoutConnected, 10000, 0, INT32_MAX );
66 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, SendBufferSize, 512*1024, 0, 0x10000000 );
67 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int64, ConnectionUserData, -1 ); // no limits here
68 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, SendRateMin, 128*1024, 1024, 0x10000000 );
69 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, SendRateMax, 1024*1024, 1024, 0x10000000 );
70 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, NagleTime, 5000, 0, 20000 );
71 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, MTU_PacketSize, 1300, k_cbSteamNetworkingSocketsMinMTUPacketSize, k_cbSteamNetworkingSocketsMaxUDPMsgLen );
72 #ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE
73 // We don't have a trusted third party, so allow this by default,
74 // and don't warn about it
75 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, IP_AllowWithoutAuth, 2, 0, 2 );
76 #else
77 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, IP_AllowWithoutAuth, 0, 0, 2 );
78 #endif
79 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, Unencrypted, 0, 0, 3 );
80 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, SymmetricConnect, 0, 0, 1 );
81 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, LocalVirtualPort, -1, -1, 65535 );
82 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, LogLevel_AckRTT, k_ESteamNetworkingSocketsDebugOutputType_Warning, k_ESteamNetworkingSocketsDebugOutputType_Error, k_ESteamNetworkingSocketsDebugOutputType_Everything );
83 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, LogLevel_PacketDecode, k_ESteamNetworkingSocketsDebugOutputType_Warning, k_ESteamNetworkingSocketsDebugOutputType_Error, k_ESteamNetworkingSocketsDebugOutputType_Everything );
84 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, LogLevel_Message, k_ESteamNetworkingSocketsDebugOutputType_Warning, k_ESteamNetworkingSocketsDebugOutputType_Error, k_ESteamNetworkingSocketsDebugOutputType_Everything );
85 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, LogLevel_PacketGaps, k_ESteamNetworkingSocketsDebugOutputType_Warning, k_ESteamNetworkingSocketsDebugOutputType_Error, k_ESteamNetworkingSocketsDebugOutputType_Everything );
86 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, LogLevel_P2PRendezvous, k_ESteamNetworkingSocketsDebugOutputType_Warning, k_ESteamNetworkingSocketsDebugOutputType_Error, k_ESteamNetworkingSocketsDebugOutputType_Everything );
87 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( void *, Callback_ConnectionStatusChanged, nullptr );
88
89 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_ICE
90 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( std::string, P2P_STUN_ServerList, "" );
91
92 COMPILE_TIME_ASSERT( k_nSteamNetworkingConfig_P2P_Transport_ICE_Enable_Default == -1 );
93 COMPILE_TIME_ASSERT( k_nSteamNetworkingConfig_P2P_Transport_ICE_Enable_Disable == 0 );
94 #ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE
95 // There is no such thing as "default" if we don't have some sort of platform
96 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, P2P_Transport_ICE_Enable, k_nSteamNetworkingConfig_P2P_Transport_ICE_Enable_All, k_nSteamNetworkingConfig_P2P_Transport_ICE_Enable_Disable, k_nSteamNetworkingConfig_P2P_Transport_ICE_Enable_All );
97 #else
98 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, P2P_Transport_ICE_Enable, k_nSteamNetworkingConfig_P2P_Transport_ICE_Enable_Default, k_nSteamNetworkingConfig_P2P_Transport_ICE_Enable_Default, k_nSteamNetworkingConfig_P2P_Transport_ICE_Enable_All );
99 #endif
100
101 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, P2P_Transport_ICE_Penalty, 0, 0, INT_MAX );
102 #endif
103
104 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
105 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( std::string, SDRClient_DebugTicketAddress, "" );
106 DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, P2P_Transport_SDR_Penalty, 0, 0, INT_MAX );
107 #endif
108
109 static GlobalConfigValueEntry *s_pFirstGlobalConfigEntry = nullptr;
110 static bool s_bConfigValueTableInitted = false;
111 static std::vector<GlobalConfigValueEntry *> s_vecConfigValueTable; // Sorted by value
112 static std::vector<GlobalConfigValueEntry *> s_vecConnectionConfigValueTable; // Sorted by offset
113
GlobalConfigValueEntry(ESteamNetworkingConfigValue eValue,const char * pszName,ESteamNetworkingConfigDataType eDataType,ESteamNetworkingConfigScope eScope,int cbOffsetOf)114 GlobalConfigValueEntry::GlobalConfigValueEntry(
115 ESteamNetworkingConfigValue eValue,
116 const char *pszName,
117 ESteamNetworkingConfigDataType eDataType,
118 ESteamNetworkingConfigScope eScope,
119 int cbOffsetOf
120 ) : m_eValue{ eValue }
121 , m_pszName{ pszName }
122 , m_eDataType{ eDataType }
123 , m_eScope{ eScope }
124 , m_cbOffsetOf{cbOffsetOf}
125 , m_pNextEntry( s_pFirstGlobalConfigEntry )
126 {
127 s_pFirstGlobalConfigEntry = this;
128 AssertMsg( !s_bConfigValueTableInitted, "Attempt to register more config values after table is already initialized" );
129 s_bConfigValueTableInitted = false;
130 }
131
EnsureConfigValueTableInitted()132 static void EnsureConfigValueTableInitted()
133 {
134 if ( s_bConfigValueTableInitted )
135 return;
136 SteamNetworkingGlobalLock scopeLock;
137 if ( s_bConfigValueTableInitted )
138 return;
139
140 for ( GlobalConfigValueEntry *p = s_pFirstGlobalConfigEntry ; p ; p = p->m_pNextEntry )
141 {
142 s_vecConfigValueTable.push_back( p );
143 if ( p->m_eScope == k_ESteamNetworkingConfig_Connection )
144 s_vecConnectionConfigValueTable.push_back( p );
145 }
146
147 // Sort in ascending order by value, so we can binary search
148 std::sort( s_vecConfigValueTable.begin(), s_vecConfigValueTable.end(),
149 []( GlobalConfigValueEntry *a, GlobalConfigValueEntry *b ) { return a->m_eValue < b->m_eValue; } );
150
151 // Sort by struct offset, so that ConnectionConfig::Init will access memory in a sane way.
152 // This doesn't really matter, though.
153 std::sort( s_vecConnectionConfigValueTable.begin(), s_vecConnectionConfigValueTable.end(),
154 []( GlobalConfigValueEntry *a, GlobalConfigValueEntry *b ) { return a->m_cbOffsetOf < b->m_cbOffsetOf; } );
155
156 // Rebuild linked list, in order, and safety check for duplicates
157 int N = len( s_vecConfigValueTable );
158 for ( int i = 1 ; i < N ; ++i )
159 {
160 s_vecConfigValueTable[i-1]->m_pNextEntry = s_vecConfigValueTable[i];
161 AssertMsg1( s_vecConfigValueTable[i-1]->m_eValue < s_vecConfigValueTable[i]->m_eValue, "Registered duplicate config value %d", s_vecConfigValueTable[i]->m_eValue );
162 }
163 s_vecConfigValueTable[N-1]->m_pNextEntry = nullptr;
164
165 s_pFirstGlobalConfigEntry = nullptr;
166 s_bConfigValueTableInitted = true;
167 }
168
FindConfigValueEntry(ESteamNetworkingConfigValue eSearchVal)169 static GlobalConfigValueEntry *FindConfigValueEntry( ESteamNetworkingConfigValue eSearchVal )
170 {
171 EnsureConfigValueTableInitted();
172
173 // Binary search
174 int l = 0;
175 int r = len( s_vecConfigValueTable )-1;
176 while ( l <= r )
177 {
178 int m = (l+r)>>1;
179 GlobalConfigValueEntry *mp = s_vecConfigValueTable[m];
180 if ( eSearchVal < mp->m_eValue )
181 r = m-1;
182 else if ( eSearchVal > mp->m_eValue )
183 l = m+1;
184 else
185 return mp;
186 }
187
188 // Not found
189 return nullptr;
190 }
191
Init(ConnectionConfig * pInherit)192 void ConnectionConfig::Init( ConnectionConfig *pInherit )
193 {
194 EnsureConfigValueTableInitted();
195
196 for ( GlobalConfigValueEntry *pEntry : s_vecConnectionConfigValueTable )
197 {
198 ConfigValueBase *pVal = (ConfigValueBase *)((intptr_t)this + pEntry->m_cbOffsetOf );
199 if ( pInherit )
200 {
201 pVal->m_pInherit = (ConfigValueBase *)((intptr_t)pInherit + pEntry->m_cbOffsetOf );
202 }
203 else
204 {
205 // Assume the relevant members are the same, no matter
206 // what type T, so just use int32 arbitrarily
207 pVal->m_pInherit = &( static_cast< GlobalConfigValueBase<int32> * >( pEntry ) )->m_value;
208 }
209 }
210 }
211
212 /////////////////////////////////////////////////////////////////////////////
213 //
214 // Table of active sockets
215 //
216 /////////////////////////////////////////////////////////////////////////////
217
218 CUtlHashMap<uint16, CSteamNetworkConnectionBase *, std::equal_to<uint16>, Identity<uint16> > g_mapConnections;
219 CUtlHashMap<int, CSteamNetworkPollGroup *, std::equal_to<int>, Identity<int> > g_mapPollGroups;
220 TableLock g_tables_lock;
221
222 // Table of active listen sockets. Listen sockets and this table are protected
223 // by the global lock.
224 CUtlHashMap<int, CSteamNetworkListenSocketBase *, std::equal_to<int>, Identity<int> > g_mapListenSockets;
225
BConnectionStateExistsToAPI(ESteamNetworkingConnectionState eState)226 static bool BConnectionStateExistsToAPI( ESteamNetworkingConnectionState eState )
227 {
228 switch ( eState )
229 {
230 default:
231 Assert( false );
232 return false;
233 case k_ESteamNetworkingConnectionState_None:
234 case k_ESteamNetworkingConnectionState_Dead:
235 case k_ESteamNetworkingConnectionState_FinWait:
236 case k_ESteamNetworkingConnectionState_Linger:
237 return false;
238
239 case k_ESteamNetworkingConnectionState_Connecting:
240 case k_ESteamNetworkingConnectionState_FindingRoute:
241 case k_ESteamNetworkingConnectionState_Connected:
242 case k_ESteamNetworkingConnectionState_ClosedByPeer:
243 case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
244 return true;
245 }
246
247 }
248
InternalGetConnectionByHandle(HSteamNetConnection sock,ConnectionScopeLock & scopeLock,const char * pszLockTag,bool bForAPI)249 static CSteamNetworkConnectionBase *InternalGetConnectionByHandle( HSteamNetConnection sock, ConnectionScopeLock &scopeLock, const char *pszLockTag, bool bForAPI )
250 {
251 if ( sock == 0 )
252 return nullptr;
253 TableScopeLock tableScopeLock( g_tables_lock );
254 int idx = g_mapConnections.Find( uint16( sock ) );
255 if ( idx == g_mapConnections.InvalidIndex() )
256 return nullptr;
257 CSteamNetworkConnectionBase *pResult = g_mapConnections[ idx ];
258 if ( !pResult )
259 {
260 AssertMsg( false, "g_mapConnections corruption!" );
261 return nullptr;
262 }
263 if ( uint16( pResult->m_hConnectionSelf ) != uint16( sock ) )
264 {
265 AssertMsg( false, "Connection map corruption!" );
266 return nullptr;
267 }
268
269 // Make sure connection is not in the process of being self-destructed
270 bool bLocked = false;
271 for (;;)
272 {
273
274 // Fetch the state of the connection. This is OK to do
275 // even if we don't have the lock.
276 ESteamNetworkingConnectionState s = pResult->GetState();
277 if ( s == k_ESteamNetworkingConnectionState_Dead )
278 break;
279 if ( bForAPI )
280 {
281 if ( !BConnectionStateExistsToAPI( s ) )
282 break;
283 }
284
285 // Have we locked already? Then we're good
286 if ( bLocked )
287 {
288 // NOTE: We unlock the table lock here, OUT OF ORDER!
289 return pResult;
290 }
291
292 // State looks good, try to lock the connection.
293 // NOTE: we still (briefly) hold the table lock!
294 // We *should* be able to totally block here
295 // without creating a deadlock, but looping here
296 // isn't so bad
297 bLocked = scopeLock.TryLock( *pResult->m_pLock, 5, pszLockTag );
298 }
299
300 // Connection found in table, but should not be returned to the caller.
301 // Unlock the connection, if we locked it
302 if ( bLocked )
303 scopeLock.Unlock();
304
305 return nullptr;
306 }
307
GetConnectionByHandle(HSteamNetConnection sock,ConnectionScopeLock & scopeLock)308 CSteamNetworkConnectionBase *GetConnectionByHandle( HSteamNetConnection sock, ConnectionScopeLock &scopeLock )
309 {
310 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
311 return InternalGetConnectionByHandle( sock, scopeLock, nullptr, false );
312 }
313
GetConnectionByHandleForAPI(HSteamNetConnection sock,ConnectionScopeLock & scopeLock,const char * pszLockTag)314 inline CSteamNetworkConnectionBase *GetConnectionByHandleForAPI( HSteamNetConnection sock, ConnectionScopeLock &scopeLock, const char *pszLockTag )
315 {
316 return InternalGetConnectionByHandle( sock, scopeLock, pszLockTag, true );
317 }
318
GetListenSocketByHandle(HSteamListenSocket sock)319 static CSteamNetworkListenSocketBase *GetListenSocketByHandle( HSteamListenSocket sock )
320 {
321 SteamNetworkingGlobalLock::AssertHeldByCurrentThread(); // listen sockets are protected by the global lock!
322 if ( sock == k_HSteamListenSocket_Invalid )
323 return nullptr;
324 AssertMsg( !(sock & 0x80000000), "A poll group handle was used where a listen socket handle was expected" );
325 int idx = sock & 0xffff;
326 if ( !g_mapListenSockets.IsValidIndex( idx ) )
327 return nullptr;
328 CSteamNetworkListenSocketBase *pResult = g_mapListenSockets[ idx ];
329 if ( pResult->m_hListenSocketSelf != sock )
330 {
331 // Slot was reused, but this handle is now invalid
332 return nullptr;
333 }
334 return pResult;
335 }
336
GetPollGroupByHandle(HSteamNetPollGroup hPollGroup,PollGroupScopeLock & scopeLock,const char * pszLockTag)337 CSteamNetworkPollGroup *GetPollGroupByHandle( HSteamNetPollGroup hPollGroup, PollGroupScopeLock &scopeLock, const char *pszLockTag )
338 {
339 if ( hPollGroup == k_HSteamNetPollGroup_Invalid )
340 return nullptr;
341 AssertMsg( (hPollGroup & 0x80000000), "A listen socket handle was used where a poll group handle was expected" );
342 int idx = hPollGroup & 0xffff;
343 TableScopeLock tableScopeLock( g_tables_lock );
344 if ( !g_mapPollGroups.IsValidIndex( idx ) )
345 return nullptr;
346 CSteamNetworkPollGroup *pResult = g_mapPollGroups[ idx ];
347
348 // Make sure poll group is the one they really asked for, and also
349 // handle deletion race condition
350 while ( pResult->m_hPollGroupSelf == hPollGroup )
351 {
352 if ( scopeLock.TryLock( pResult->m_lock, 1, pszLockTag ) )
353 return pResult;
354 }
355
356 // Slot was reused, but this handle is now invalid,
357 // or poll group deleted race condition
358 return nullptr;
359 }
360
361 /////////////////////////////////////////////////////////////////////////////
362 //
363 // CSteamSocketNetworkingBase
364 //
365 /////////////////////////////////////////////////////////////////////////////
366
367 std::vector<CSteamNetworkingSockets *> CSteamNetworkingSockets::s_vecSteamNetworkingSocketsInstances;
368
CSteamNetworkingSockets(CSteamNetworkingUtils * pSteamNetworkingUtils)369 CSteamNetworkingSockets::CSteamNetworkingSockets( CSteamNetworkingUtils *pSteamNetworkingUtils )
370 : m_bHaveLowLevelRef( false )
371 , m_pSteamNetworkingUtils( pSteamNetworkingUtils )
372 , m_pSteamNetworkingMessages( nullptr )
373 , m_bEverTriedToGetCert( false )
374 , m_bEverGotCert( false )
375 #ifdef STEAMNETWORKINGSOCKETS_CAN_REQUEST_CERT
376 , m_scheduleCheckRenewCert( this, &CSteamNetworkingSockets::CheckAuthenticationPrerequisites )
377 #endif
378 , m_mutexPendingCallbacks( "pending_callbacks" )
379 {
380 m_connectionConfig.Init( nullptr );
381 InternalInitIdentity();
382 }
383
InternalInitIdentity()384 void CSteamNetworkingSockets::InternalInitIdentity()
385 {
386 m_identity.Clear();
387 m_msgSignedCert.Clear();
388 m_msgCert.Clear();
389 m_keyPrivateKey.Wipe();
390
391 #ifdef STEAMNETWORKINGSOCKETS_CAN_REQUEST_CERT
392 m_CertStatus.m_eAvail = k_ESteamNetworkingAvailability_NeverTried;
393 m_CertStatus.m_debugMsg[0] = '\0';
394 #else
395 m_CertStatus.m_eAvail = k_ESteamNetworkingAvailability_CannotTry;
396 V_strcpy_safe( m_CertStatus.m_debugMsg, "No certificate authority" );
397 #endif
398 m_AuthenticationStatus = m_CertStatus;
399 m_bEverTriedToGetCert = false;
400 m_bEverGotCert = false;
401 }
402
~CSteamNetworkingSockets()403 CSteamNetworkingSockets::~CSteamNetworkingSockets()
404 {
405 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
406 Assert( !m_bHaveLowLevelRef ); // Called destructor directly? Use Destroy()!
407 }
408
409 #ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE
BInitGameNetworkingSockets(const SteamNetworkingIdentity * pIdentity,SteamDatagramErrMsg & errMsg)410 bool CSteamNetworkingSockets::BInitGameNetworkingSockets( const SteamNetworkingIdentity *pIdentity, SteamDatagramErrMsg &errMsg )
411 {
412 AssertMsg( !m_bHaveLowLevelRef, "Initted interface twice?" );
413
414 // Make sure low level socket support is ready
415 if ( !BInitLowLevel( errMsg ) )
416 return false;
417
418 if ( pIdentity )
419 m_identity = *pIdentity;
420 else
421 CacheIdentity();
422
423 return true;
424 }
425 #endif
426
BInitLowLevel(SteamNetworkingErrMsg & errMsg)427 bool CSteamNetworkingSockets::BInitLowLevel( SteamNetworkingErrMsg &errMsg )
428 {
429 if ( m_bHaveLowLevelRef )
430 return true;
431 if ( !BSteamNetworkingSocketsLowLevelAddRef( errMsg) )
432 return false;
433
434 // Add us to list of extant instances only after we have done some initialization
435 if ( !has_element( s_vecSteamNetworkingSocketsInstances, this ) )
436 s_vecSteamNetworkingSocketsInstances.push_back( this );
437
438 m_bHaveLowLevelRef = true;
439 return true;
440 }
441
KillConnections()442 void CSteamNetworkingSockets::KillConnections()
443 {
444 SteamNetworkingGlobalLock::AssertHeldByCurrentThread( "CSteamNetworkingSockets::KillConnections" );
445 TableScopeLock tableScopeLock( g_tables_lock );
446
447 // Warn messages interface that it needs to clean up. We need to do this
448 // because that class has pointers to objects that we are about to destroy.
449 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_STEAMNETWORKINGMESSAGES
450 if ( m_pSteamNetworkingMessages )
451 m_pSteamNetworkingMessages->FreeResources();
452 #endif
453
454 // Destroy all of my connections
455 CSteamNetworkConnectionBase::ProcessDeletionList();
456 FOR_EACH_HASHMAP( g_mapConnections, idx )
457 {
458 CSteamNetworkConnectionBase *pConn = g_mapConnections[idx];
459 if ( pConn->m_pSteamNetworkingSocketsInterface == this )
460 {
461 ConnectionScopeLock connectionLock( *pConn );
462 pConn->ConnectionQueueDestroy();
463 }
464 }
465 CSteamNetworkConnectionBase::ProcessDeletionList();
466
467 // Destroy all of my listen sockets
468 FOR_EACH_HASHMAP( g_mapListenSockets, idx )
469 {
470 CSteamNetworkListenSocketBase *pSock = g_mapListenSockets[idx];
471 if ( pSock->m_pSteamNetworkingSocketsInterface == this )
472 {
473 DbgVerify( CloseListenSocket( pSock->m_hListenSocketSelf ) );
474 Assert( !g_mapListenSockets.IsValidIndex( idx ) );
475 }
476 }
477
478 // Destroy all of my poll groups
479 FOR_EACH_HASHMAP( g_mapPollGroups, idx )
480 {
481 CSteamNetworkPollGroup *pPollGroup = g_mapPollGroups[idx];
482 if ( pPollGroup->m_pSteamNetworkingSocketsInterface == this )
483 {
484 DbgVerify( DestroyPollGroup( pPollGroup->m_hPollGroupSelf ) );
485 Assert( !g_mapPollGroups.IsValidIndex( idx ) );
486 }
487 }
488 }
489
Destroy()490 void CSteamNetworkingSockets::Destroy()
491 {
492 SteamNetworkingGlobalLock::AssertHeldByCurrentThread( "CSteamNetworkingSockets::Destroy" );
493
494 FreeResources();
495
496 // Nuke messages interface, if we had one.
497 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_STEAMNETWORKINGMESSAGES
498 if ( m_pSteamNetworkingMessages )
499 {
500 delete m_pSteamNetworkingMessages;
501 Assert( m_pSteamNetworkingMessages == nullptr ); // Destructor should sever this link
502 m_pSteamNetworkingMessages = nullptr; // Buuuuut we'll slam it, too, in case there's a bug
503 }
504 #endif
505
506 // Remove from list of extant instances, if we are there
507 find_and_remove_element( s_vecSteamNetworkingSocketsInstances, this );
508
509 delete this;
510 }
511
FreeResources()512 void CSteamNetworkingSockets::FreeResources()
513 {
514
515 KillConnections();
516
517 // Clear identity and crypto stuff.
518 // If we are re-initialized, we might get new ones
519 InternalInitIdentity();
520
521 // Mark us as no longer being setup
522 if ( m_bHaveLowLevelRef )
523 {
524 m_bHaveLowLevelRef = false;
525 SteamNetworkingSocketsLowLevelDecRef();
526 }
527 }
528
BHasAnyConnections() const529 bool CSteamNetworkingSockets::BHasAnyConnections() const
530 {
531 TableScopeLock tableScopeLock( g_tables_lock );
532 for ( CSteamNetworkConnectionBase *pConn: g_mapConnections.IterValues() )
533 {
534 if ( pConn->m_pSteamNetworkingSocketsInterface == this )
535 return true;
536 }
537 return false;
538 }
539
BHasAnyListenSockets() const540 bool CSteamNetworkingSockets::BHasAnyListenSockets() const
541 {
542 TableScopeLock tableScopeLock( g_tables_lock );
543 for ( CSteamNetworkListenSocketBase *pSock: g_mapListenSockets.IterValues() )
544 {
545 if ( pSock->m_pSteamNetworkingSocketsInterface == this )
546 return true;
547 }
548 return false;
549 }
550
GetIdentity(SteamNetworkingIdentity * pIdentity)551 bool CSteamNetworkingSockets::GetIdentity( SteamNetworkingIdentity *pIdentity )
552 {
553 SteamNetworkingGlobalLock scopeLock( "GetIdentity" );
554 InternalGetIdentity();
555 if ( pIdentity )
556 *pIdentity = m_identity;
557 return !m_identity.IsInvalid();
558 }
559
GetSecondsUntilCertExpiry() const560 int CSteamNetworkingSockets::GetSecondsUntilCertExpiry() const
561 {
562 if ( !m_msgSignedCert.has_cert() )
563 return INT_MIN;
564
565 Assert( m_msgSignedCert.has_ca_signature() ); // Connections may use unsigned certs in certain situations, but we never use them here
566 Assert( m_msgCert.has_key_data() );
567 Assert( m_msgCert.has_time_expiry() ); // We should never generate keys without an expiry!
568
569 int nSeconduntilExpiry = (long)m_msgCert.time_expiry() - (long)m_pSteamNetworkingUtils->GetTimeSecure();
570 return nSeconduntilExpiry;
571 }
572
GetCertificateRequest(int * pcbBlob,void * pBlob,SteamNetworkingErrMsg & errMsg)573 bool CSteamNetworkingSockets::GetCertificateRequest( int *pcbBlob, void *pBlob, SteamNetworkingErrMsg &errMsg )
574 {
575 SteamNetworkingGlobalLock scopeLock( "GetCertificateRequest" );
576
577 // If we don't have a private key, generate one now.
578 CECSigningPublicKey pubKey;
579 if ( m_keyPrivateKey.IsValid() )
580 {
581 DbgVerify( m_keyPrivateKey.GetPublicKey( &pubKey ) );
582 }
583 else
584 {
585 CCrypto::GenerateSigningKeyPair( &pubKey, &m_keyPrivateKey );
586 }
587
588 // Fill out the request
589 CMsgSteamDatagramCertificateRequest msgRequest;
590 CMsgSteamDatagramCertificate &msgCert =*msgRequest.mutable_cert();
591
592 // Our public key
593 msgCert.set_key_type( CMsgSteamDatagramCertificate_EKeyType_ED25519 );
594 DbgVerify( pubKey.GetRawDataAsStdString( msgCert.mutable_key_data() ) );
595
596 // Our identity, if we know it
597 InternalGetIdentity();
598 if ( !m_identity.IsInvalid() && !m_identity.IsLocalHost() )
599 {
600 SteamNetworkingIdentityToProtobuf( m_identity, msgCert, identity_string, legacy_identity_binary, legacy_steam_id );
601 }
602
603 // Check size
604 int cb = ProtoMsgByteSize( msgRequest );
605 if ( !pBlob )
606 {
607 *pcbBlob = cb;
608 return true;
609 }
610 if ( cb > *pcbBlob )
611 {
612 *pcbBlob = cb;
613 V_sprintf_safe( errMsg, "%d byte buffer not big enough; %d bytes required", *pcbBlob, cb );
614 return false;
615 }
616
617 *pcbBlob = cb;
618 uint8 *p = (uint8 *)pBlob;
619 DbgVerify( msgRequest.SerializeWithCachedSizesToArray( p ) == p + cb );
620 return true;
621 }
622
SetCertificate(const void * pCertificate,int cbCertificate,SteamNetworkingErrMsg & errMsg)623 bool CSteamNetworkingSockets::SetCertificate( const void *pCertificate, int cbCertificate, SteamNetworkingErrMsg &errMsg )
624 {
625 // Crack the blob
626 CMsgSteamDatagramCertificateSigned msgCertSigned;
627 if ( !msgCertSigned.ParseFromArray( pCertificate, cbCertificate ) )
628 {
629 V_strcpy_safe( errMsg, "CMsgSteamDatagramCertificateSigned failed protobuf parse" );
630 return false;
631 }
632
633 SteamNetworkingGlobalLock scopeLock( "SetCertificate" );
634
635 // Crack the cert, and check the signature. If *we* aren't even willing
636 // to trust it, assume that our peers won't either
637 CMsgSteamDatagramCertificate msgCert;
638 time_t authTime = m_pSteamNetworkingUtils->GetTimeSecure();
639 const CertAuthScope *pAuthScope = CertStore_CheckCert( msgCertSigned, msgCert, authTime, errMsg );
640 if ( !pAuthScope )
641 {
642 SpewWarning( "SetCertificate: We are not currently able to verify our own cert! %s. Continuing anyway!", errMsg );
643 }
644
645 // Extract the identity from the cert
646 SteamNetworkingErrMsg tempErrMsg;
647 SteamNetworkingIdentity certIdentity;
648 int r = SteamNetworkingIdentityFromCert( certIdentity, msgCert, tempErrMsg );
649 if ( r < 0 )
650 {
651 V_sprintf_safe( errMsg, "Cert has invalid identity. %s", tempErrMsg );
652 return false;
653 }
654
655 // We currently only support one key type
656 if ( msgCert.key_type() != CMsgSteamDatagramCertificate_EKeyType_ED25519 || msgCert.key_data().size() != 32 )
657 {
658 V_strcpy_safe( errMsg, "Cert has invalid public key" );
659 return false;
660 }
661
662 // Does cert contain a private key?
663 if ( msgCertSigned.has_private_key_data() )
664 {
665 // The degree to which the key is actually "private" is not
666 // really known to us. However there are some use cases where
667 // we will accept a cert
668 const std::string &private_key_data = msgCertSigned.private_key_data();
669 if ( m_keyPrivateKey.IsValid() )
670 {
671
672 // We already chose a private key, so the cert must match.
673 // For the most common use cases, we choose a private
674 // key and it never leaves the current process.
675 if ( m_keyPrivateKey.GetRawDataSize() != private_key_data.length()
676 || memcmp( m_keyPrivateKey.GetRawDataPtr(), private_key_data.c_str(), private_key_data.length() ) != 0 )
677 {
678 V_strcpy_safe( errMsg, "Private key mismatch" );
679 return false;
680 }
681 }
682 else
683 {
684 // We haven't chosen a private key yet, so we'll accept this one.
685 if ( !m_keyPrivateKey.SetRawDataFromStdString( private_key_data ) )
686 {
687 V_strcpy_safe( errMsg, "Invalid private key" );
688 return false;
689 }
690 }
691 }
692 else if ( !m_keyPrivateKey.IsValid() )
693 {
694 // WAT
695 V_strcpy_safe( errMsg, "Cannot set cert. No private key?" );
696 return false;
697 }
698
699 // Make sure the cert actually matches our public key.
700 if ( memcmp( msgCert.key_data().c_str(), m_keyPrivateKey.GetPublicKeyRawData(), 32 ) != 0 )
701 {
702 V_strcpy_safe( errMsg, "Cert public key does not match our private key" );
703 return false;
704 }
705
706 // Make sure the cert authorizes us for the App we think we are running
707 AppId_t nAppID = m_pSteamNetworkingUtils->GetAppID();
708 if ( !CheckCertAppID( msgCert, pAuthScope, nAppID, tempErrMsg ) )
709 {
710 V_sprintf_safe( errMsg, "Cert does not authorize us for App %u", nAppID );
711 return false;
712 }
713
714 // If we don't know our identity, then set it now. Otherwise,
715 // it better match.
716 if ( m_identity.IsInvalid() || m_identity.IsLocalHost() )
717 {
718 m_identity = certIdentity;
719 SpewMsg( "Local identity established from certificate. We are '%s'\n", SteamNetworkingIdentityRender( m_identity ).c_str() );
720 }
721 else if ( !( m_identity == certIdentity ) )
722 {
723 V_sprintf_safe( errMsg, "Cert is for identity '%s'. We are '%s'", SteamNetworkingIdentityRender( certIdentity ).c_str(), SteamNetworkingIdentityRender( m_identity ).c_str() );
724 return false;
725 }
726
727 // Save it off
728 m_msgSignedCert = std::move( msgCertSigned );
729 m_msgCert = std::move( msgCert );
730 // If shouldn't already be expired.
731 AssertMsg( GetSecondsUntilCertExpiry() > 0, "Cert already invalid / expired?" );
732
733 // We've got a valid cert
734 SetCertStatus( k_ESteamNetworkingAvailability_Current, "OK" );
735
736 // Make sure we have everything else we need to do authentication.
737 // This will also make sure we have renewal scheduled
738 AuthenticationNeeded();
739
740 // OK
741 return true;
742 }
743
ResetIdentity(const SteamNetworkingIdentity * pIdentity)744 void CSteamNetworkingSockets::ResetIdentity( const SteamNetworkingIdentity *pIdentity )
745 {
746 #ifdef STEAMNETWORKINGSOCKETS_STEAM
747 Assert( !"Not supported on steam" );
748 #else
749 KillConnections();
750 InternalInitIdentity();
751 if ( pIdentity )
752 m_identity = *pIdentity;
753 #endif
754 }
755
InitAuthentication()756 ESteamNetworkingAvailability CSteamNetworkingSockets::InitAuthentication()
757 {
758 SteamNetworkingGlobalLock scopeLock( "InitAuthentication" );
759
760 // Check/fetch prerequisites
761 AuthenticationNeeded();
762
763 // Return status
764 return m_AuthenticationStatus.m_eAvail;
765 }
766
CheckAuthenticationPrerequisites(SteamNetworkingMicroseconds usecNow)767 void CSteamNetworkingSockets::CheckAuthenticationPrerequisites( SteamNetworkingMicroseconds usecNow )
768 {
769 #ifdef STEAMNETWORKINGSOCKETS_CAN_REQUEST_CERT
770 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
771
772 // Check if we're in flight already.
773 bool bInFlight = BCertRequestInFlight();
774
775 // Do we already have a cert?
776 if ( m_msgSignedCert.has_cert() )
777 {
778 //Assert( m_CertStatus.m_eAvail == k_ESteamNetworkingAvailability_Current );
779
780 // How much more life does it have in it?
781 int nSeconduntilExpiry = GetSecondsUntilCertExpiry();
782 if ( nSeconduntilExpiry < 0 )
783 {
784
785 // It's already expired, we might as well discard it now.
786 SpewMsg( "Cert expired %d seconds ago. Discarding and requesting another\n", -nSeconduntilExpiry );
787 m_msgSignedCert.Clear();
788 m_msgCert.Clear();
789 m_keyPrivateKey.Wipe();
790
791 // Update cert status
792 SetCertStatus( k_ESteamNetworkingAvailability_Previously, "Expired" );
793 }
794 else
795 {
796
797 // If request is already active, don't do any of the work below, and don't spam while we wait, since this function may be called frequently.
798 if ( bInFlight )
799 return;
800
801 // Check if it's time to renew
802 SteamNetworkingMicroseconds usecTargetRenew = usecNow + ( nSeconduntilExpiry - k_nSecCertExpirySeekRenew ) * k_nMillion;
803 if ( usecTargetRenew > usecNow )
804 {
805 SteamNetworkingMicroseconds usecScheduledRenew = m_scheduleCheckRenewCert.GetScheduleTime();
806 SteamNetworkingMicroseconds usecLatestRenew = usecTargetRenew + 4*k_nMillion;
807 if ( usecScheduledRenew <= usecLatestRenew )
808 {
809 // Currently scheduled time is good enough. Don't constantly update the schedule time,
810 // that involves a (small amount) of work. Just wait for it
811 }
812 else
813 {
814 // Schedule a check later
815 m_scheduleCheckRenewCert.Schedule( usecTargetRenew + 2*k_nMillion );
816 }
817 return;
818 }
819
820 // Currently valid, but it's time to renew. Spew about this.
821 SpewMsg( "Cert expires in %d seconds. Requesting another, but keeping current cert in case request fails\n", nSeconduntilExpiry );
822 }
823 }
824
825 // If a request is already active, then we just need to wait for it to complete
826 if ( bInFlight )
827 return;
828
829 // Invoke platform code to begin fetching a cert
830 BeginFetchCertAsync();
831 #endif
832 }
833
SetCertStatus(ESteamNetworkingAvailability eAvail,const char * pszFmt,...)834 void CSteamNetworkingSockets::SetCertStatus( ESteamNetworkingAvailability eAvail, const char *pszFmt, ... )
835 {
836 char msg[ sizeof(m_CertStatus.m_debugMsg) ];
837 va_list ap;
838 va_start( ap, pszFmt );
839 V_vsprintf_safe( msg, pszFmt, ap );
840 va_end( ap );
841
842 // Mark success or an attempt
843 if ( eAvail == k_ESteamNetworkingAvailability_Current )
844 m_bEverGotCert = true;
845 if ( eAvail == k_ESteamNetworkingAvailability_Attempting || eAvail == k_ESteamNetworkingAvailability_Retrying )
846 m_bEverTriedToGetCert = true;
847
848 // If we failed, but we previously succeeded, convert to "previously"
849 if ( eAvail == k_ESteamNetworkingAvailability_Failed && m_bEverGotCert )
850 eAvail = k_ESteamNetworkingAvailability_Previously;
851
852 // No change?
853 if ( m_CertStatus.m_eAvail == eAvail && V_stricmp( m_CertStatus.m_debugMsg, msg ) == 0 )
854 return;
855
856 // Update
857 m_CertStatus.m_eAvail = eAvail;
858 V_strcpy_safe( m_CertStatus.m_debugMsg, msg );
859
860 // Check if our high level authentication status changed
861 DeduceAuthenticationStatus();
862 }
863
DeduceAuthenticationStatus()864 void CSteamNetworkingSockets::DeduceAuthenticationStatus()
865 {
866 // For the base class, the overall authentication status is identical to the status of
867 // our cert. (Derived classes may add additional criteria)
868 SetAuthenticationStatus( m_CertStatus );
869 }
870
SetAuthenticationStatus(const SteamNetAuthenticationStatus_t & newStatus)871 void CSteamNetworkingSockets::SetAuthenticationStatus( const SteamNetAuthenticationStatus_t &newStatus )
872 {
873 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
874
875 // No change?
876 bool bStatusChanged = newStatus.m_eAvail != m_AuthenticationStatus.m_eAvail;
877 if ( !bStatusChanged && V_strcmp( m_AuthenticationStatus.m_debugMsg, newStatus.m_debugMsg ) == 0 )
878 return;
879
880 // Update
881 m_AuthenticationStatus = newStatus;
882
883 // Re-cache identity
884 InternalGetIdentity();
885
886 // Post a callback, but only if the high level status changed. Don't post a callback just
887 // because the message changed
888 if ( bStatusChanged )
889 {
890 // Spew
891 SpewMsg( "AuthStatus (%s): %s (%s)",
892 SteamNetworkingIdentityRender( m_identity ).c_str(),
893 GetAvailabilityString( m_AuthenticationStatus.m_eAvail ), m_AuthenticationStatus.m_debugMsg );
894
895 QueueCallback( m_AuthenticationStatus, g_Config_Callback_AuthStatusChanged.Get() );
896 }
897 }
898
899 #ifdef STEAMNETWORKINGSOCKETS_CAN_REQUEST_CERT
AsyncCertRequestFinished()900 void CSteamNetworkingSockets::AsyncCertRequestFinished()
901 {
902 SteamNetworkingGlobalLock::AssertHeldByCurrentThread( "AsyncCertRequestFinished" );
903
904 Assert( m_msgSignedCert.has_cert() );
905 SetCertStatus( k_ESteamNetworkingAvailability_Current, "OK" );
906
907 // Check for any connections that we own that are waiting on a cert
908 TableScopeLock tableScopeLock( g_tables_lock );
909 for ( CSteamNetworkConnectionBase *pConn: g_mapConnections.IterValues() )
910 {
911 if ( pConn->m_pSteamNetworkingSocketsInterface == this )
912 pConn->InterfaceGotCert();
913 }
914 }
915
CertRequestFailed(ESteamNetworkingAvailability eCertAvail,ESteamNetConnectionEnd nConnectionEndReason,const char * pszMsg)916 void CSteamNetworkingSockets::CertRequestFailed( ESteamNetworkingAvailability eCertAvail, ESteamNetConnectionEnd nConnectionEndReason, const char *pszMsg )
917 {
918 SteamNetworkingGlobalLock::AssertHeldByCurrentThread( "CertRequestFailed" );
919
920 SpewWarning( "Cert request for %s failed with reason code %d. %s\n", SteamNetworkingIdentityRender( InternalGetIdentity() ).c_str(), nConnectionEndReason, pszMsg );
921
922 // Schedule a retry. Note that if we have active connections that need for a cert,
923 // we may end up retrying sooner. If we don't have any active connections, spamming
924 // retries way too frequently may be really bad; we might end up DoS-ing ourselves.
925 // Do we need to make this configurable?
926 m_scheduleCheckRenewCert.Schedule( SteamNetworkingSockets_GetLocalTimestamp() + k_nMillion*30 );
927
928 if ( m_msgSignedCert.has_cert() )
929 {
930 SpewMsg( "But we still have a valid cert, continuing with that one\n" );
931 AsyncCertRequestFinished();
932 return;
933 }
934
935 // Set generic cert status, so we will post a callback
936 SetCertStatus( eCertAvail, "%s", pszMsg );
937
938 TableScopeLock tableScopeLock( g_tables_lock );
939 for ( CSteamNetworkConnectionBase *pConn: g_mapConnections.IterValues() )
940 {
941 if ( pConn->m_pSteamNetworkingSocketsInterface == this )
942 pConn->CertRequestFailed( nConnectionEndReason, pszMsg );
943 }
944
945 // FIXME If we have any listen sockets, we might need to let them know about this as well?
946 }
947 #endif
948
GetAuthenticationStatus(SteamNetAuthenticationStatus_t * pDetails)949 ESteamNetworkingAvailability CSteamNetworkingSockets::GetAuthenticationStatus( SteamNetAuthenticationStatus_t *pDetails )
950 {
951 SteamNetworkingGlobalLock scopeLock; // !SPEED! We could protect this with a more tightly scoped lock, if we think this is eomthing people might be polling
952
953 // Return details, if requested
954 if ( pDetails )
955 *pDetails = m_AuthenticationStatus;
956
957 // Return status
958 return m_AuthenticationStatus.m_eAvail;
959 }
960
CreateListenSocketIP(const SteamNetworkingIPAddr & localAddr,int nOptions,const SteamNetworkingConfigValue_t * pOptions)961 HSteamListenSocket CSteamNetworkingSockets::CreateListenSocketIP( const SteamNetworkingIPAddr &localAddr, int nOptions, const SteamNetworkingConfigValue_t *pOptions )
962 {
963 SteamNetworkingGlobalLock scopeLock( "CreateListenSocketIP" );
964 SteamDatagramErrMsg errMsg;
965
966 CSteamNetworkListenSocketDirectUDP *pSock = new CSteamNetworkListenSocketDirectUDP( this );
967 if ( !pSock )
968 return k_HSteamListenSocket_Invalid;
969 if ( !pSock->BInit( localAddr, nOptions, pOptions, errMsg ) )
970 {
971 SpewError( "Cannot create listen socket. %s", errMsg );
972 pSock->Destroy();
973 return k_HSteamListenSocket_Invalid;
974 }
975
976 return pSock->m_hListenSocketSelf;
977 }
978
ConnectByIPAddress(const SteamNetworkingIPAddr & address,int nOptions,const SteamNetworkingConfigValue_t * pOptions)979 HSteamNetConnection CSteamNetworkingSockets::ConnectByIPAddress( const SteamNetworkingIPAddr &address, int nOptions, const SteamNetworkingConfigValue_t *pOptions )
980 {
981 SteamNetworkingGlobalLock scopeLock( "ConnectByIPAddress" );
982 ConnectionScopeLock connectionLock;
983 CSteamNetworkConnectionUDP *pConn = new CSteamNetworkConnectionUDP( this, connectionLock );
984 if ( !pConn )
985 return k_HSteamNetConnection_Invalid;
986 SteamDatagramErrMsg errMsg;
987 if ( !pConn->BInitConnect( address, nOptions, pOptions, errMsg ) )
988 {
989 SpewError( "Cannot create IPv4 connection. %s", errMsg );
990 pConn->ConnectionQueueDestroy();
991 return k_HSteamNetConnection_Invalid;
992 }
993
994 return pConn->m_hConnectionSelf;
995 }
996
997
AcceptConnection(HSteamNetConnection hConn)998 EResult CSteamNetworkingSockets::AcceptConnection( HSteamNetConnection hConn )
999 {
1000 SteamNetworkingGlobalLock scopeLock( "AcceptConnection" ); // Take global lock, since this will lead to connection state transition
1001 ConnectionScopeLock connectionLock;
1002 CSteamNetworkConnectionBase *pConn = GetConnectionByHandleForAPI( hConn, connectionLock, nullptr );
1003 if ( !pConn )
1004 {
1005 SpewError( "Cannot accept connection #%u; invalid connection handle", hConn );
1006 return k_EResultInvalidParam;
1007 }
1008
1009 // Accept it
1010 return pConn->APIAcceptConnection();
1011 }
1012
CloseConnection(HSteamNetConnection hConn,int nReason,const char * pszDebug,bool bEnableLinger)1013 bool CSteamNetworkingSockets::CloseConnection( HSteamNetConnection hConn, int nReason, const char *pszDebug, bool bEnableLinger )
1014 {
1015 SteamNetworkingGlobalLock scopeLock( "CloseConnection" ); // Take global lock, we are going to change connection state and/or destroy objects
1016 ConnectionScopeLock connectionLock;
1017 CSteamNetworkConnectionBase *pConn = GetConnectionByHandleForAPI( hConn, connectionLock, nullptr );
1018 if ( !pConn )
1019 return false;
1020
1021 // Close it
1022 pConn->APICloseConnection( nReason, pszDebug, bEnableLinger );
1023 return true;
1024 }
1025
CloseListenSocket(HSteamListenSocket hSocket)1026 bool CSteamNetworkingSockets::CloseListenSocket( HSteamListenSocket hSocket )
1027 {
1028 SteamNetworkingGlobalLock scopeLock( "CloseListenSocket" ); // Take global lock, we are going to destroy objects
1029 CSteamNetworkListenSocketBase *pSock = GetListenSocketByHandle( hSocket );
1030 if ( !pSock )
1031 return false;
1032
1033 // Delete the socket itself
1034 // NOTE: If you change this, look at CSteamSocketNetworking::Kill()!
1035 pSock->Destroy();
1036 return true;
1037 }
1038
SetConnectionUserData(HSteamNetConnection hPeer,int64 nUserData)1039 bool CSteamNetworkingSockets::SetConnectionUserData( HSteamNetConnection hPeer, int64 nUserData )
1040 {
1041 //SteamNetworkingGlobalLock scopeLock( "SetConnectionUserData" ); // NO, not necessary!
1042 ConnectionScopeLock connectionLock;
1043 CSteamNetworkConnectionBase *pConn = GetConnectionByHandleForAPI( hPeer, connectionLock, "SetConnectionUserData" );
1044 if ( !pConn )
1045 return false;
1046 pConn->SetUserData( nUserData );
1047 return true;
1048 }
1049
GetConnectionUserData(HSteamNetConnection hPeer)1050 int64 CSteamNetworkingSockets::GetConnectionUserData( HSteamNetConnection hPeer )
1051 {
1052 //SteamNetworkingGlobalLock scopeLock( "GetConnectionUserData" ); // NO, not necessary!
1053 ConnectionScopeLock connectionLock;
1054 CSteamNetworkConnectionBase *pConn = GetConnectionByHandleForAPI( hPeer, connectionLock, "GetConnectionUserData" );
1055 if ( !pConn )
1056 return -1;
1057 return pConn->GetUserData();
1058 }
1059
SetConnectionName(HSteamNetConnection hPeer,const char * pszName)1060 void CSteamNetworkingSockets::SetConnectionName( HSteamNetConnection hPeer, const char *pszName )
1061 {
1062 SteamNetworkingGlobalLock scopeLock( "SetConnectionName" ); // NOTE: Yes, we must take global lock for this. See CSteamNetworkConnectionBase::SetDescription
1063 ConnectionScopeLock connectionLock;
1064 CSteamNetworkConnectionBase *pConn = GetConnectionByHandleForAPI( hPeer, connectionLock, nullptr );
1065 if ( !pConn )
1066 return;
1067 pConn->SetAppName( pszName );
1068 }
1069
GetConnectionName(HSteamNetConnection hPeer,char * pszName,int nMaxLen)1070 bool CSteamNetworkingSockets::GetConnectionName( HSteamNetConnection hPeer, char *pszName, int nMaxLen )
1071 {
1072 //SteamNetworkingGlobalLock scopeLock( "GetConnectionName" ); // NO, not necessary!
1073 ConnectionScopeLock connectionLock;
1074 CSteamNetworkConnectionBase *pConn = GetConnectionByHandleForAPI( hPeer, connectionLock, "GetConnectionName" );
1075 if ( !pConn )
1076 return false;
1077 V_strncpy( pszName, pConn->GetAppName(), nMaxLen );
1078 return true;
1079 }
1080
SendMessageToConnection(HSteamNetConnection hConn,const void * pData,uint32 cbData,int nSendFlags,int64 * pOutMessageNumber)1081 EResult CSteamNetworkingSockets::SendMessageToConnection( HSteamNetConnection hConn, const void *pData, uint32 cbData, int nSendFlags, int64 *pOutMessageNumber )
1082 {
1083 //SteamNetworkingGlobalLock scopeLock( "SendMessageToConnection" ); // NO, not necessary!
1084 ConnectionScopeLock connectionLock;
1085 CSteamNetworkConnectionBase *pConn = GetConnectionByHandleForAPI( hConn, connectionLock, "SendMessageToConnection" );
1086 if ( !pConn )
1087 return k_EResultInvalidParam;
1088 return pConn->APISendMessageToConnection( pData, cbData, nSendFlags, pOutMessageNumber );
1089 }
1090
SendMessages(int nMessages,SteamNetworkingMessage_t * const * pMessages,int64 * pOutMessageNumberOrResult)1091 void CSteamNetworkingSockets::SendMessages( int nMessages, SteamNetworkingMessage_t *const *pMessages, int64 *pOutMessageNumberOrResult )
1092 {
1093
1094 // Get list of messages, grouped by connection.
1095 // But within the connection, it is important that we
1096 // keep them in the same order!
1097 struct SortMsg_t
1098 {
1099 HSteamNetConnection m_hConn;
1100 int m_idx;
1101 inline bool operator<(const SortMsg_t &x ) const
1102 {
1103 if ( m_hConn < x.m_hConn ) return true;
1104 if ( m_hConn > x.m_hConn ) return false;
1105 return m_idx < x.m_idx;
1106 }
1107 };
1108 SortMsg_t *pSortMessages = (SortMsg_t *)alloca( nMessages * sizeof(SortMsg_t) );
1109 int nSortMessages = 0;
1110
1111 for ( int i = 0 ; i < nMessages ; ++i )
1112 {
1113
1114 // Sanity check that message is valid
1115 CSteamNetworkingMessage *pMsg = static_cast<CSteamNetworkingMessage*>( pMessages[i] );
1116 if ( !pMsg )
1117 {
1118 if ( pOutMessageNumberOrResult )
1119 pOutMessageNumberOrResult[i] = -k_EResultInvalidParam;
1120 continue;
1121 }
1122
1123 if ( pMsg->m_conn == k_HSteamNetConnection_Invalid )
1124 {
1125 if ( pOutMessageNumberOrResult )
1126 pOutMessageNumberOrResult[i] = -k_EResultInvalidParam;
1127 pMsg->Release();
1128 continue;
1129 }
1130
1131 pSortMessages[ nSortMessages ].m_hConn = pMsg->m_conn;
1132 pSortMessages[ nSortMessages ].m_idx = i;
1133 ++nSortMessages;
1134 }
1135
1136 if ( nSortMessages < 1 )
1137 return;
1138
1139 SortMsg_t *const pSortEnd = pSortMessages+nSortMessages;
1140 std::sort( pSortMessages, pSortEnd );
1141
1142 // OK, we are ready to begin
1143
1144 // SteamNetworkingGlobalLock scopeLock( "SendMessages" ); // NO, not necessary!
1145 SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
1146
1147 CSteamNetworkConnectionBase *pConn = nullptr;
1148 HSteamNetConnection hConn = k_HSteamNetConnection_Invalid;
1149 ConnectionScopeLock connectionLock;
1150 bool bConnectionThinkImmediately = false;
1151 for ( SortMsg_t *pSort = pSortMessages ; pSort < pSortEnd ; ++pSort )
1152 {
1153
1154 // Switched to a different connection?
1155 if ( hConn != pSort->m_hConn )
1156 {
1157
1158 // Flush out previous connection, if any
1159 if ( pConn )
1160 {
1161 if ( bConnectionThinkImmediately )
1162 pConn->CheckConnectionStateOrScheduleWakeUp( usecNow );
1163 connectionLock.Unlock();
1164 bConnectionThinkImmediately = false;
1165 }
1166
1167 // Locate the connection
1168 hConn = pSort->m_hConn;
1169 pConn = GetConnectionByHandleForAPI( hConn, connectionLock, "SendMessages" );
1170 }
1171
1172 CSteamNetworkingMessage *pMsg = static_cast<CSteamNetworkingMessage*>( pMessages[pSort->m_idx] );
1173
1174 // Current connection is valid?
1175 int64 result;
1176 if ( pConn )
1177 {
1178
1179 // Attempt to send
1180 bool bThinkImmediately = false;
1181 result = pConn->APISendMessageToConnection( pMsg, usecNow, &bThinkImmediately );
1182 if ( bThinkImmediately )
1183 bConnectionThinkImmediately = true;
1184 }
1185 else
1186 {
1187 pMsg->Release();
1188 result = -k_EResultInvalidParam;
1189 }
1190
1191 // Return result for this message if they asked for it
1192 if ( pOutMessageNumberOrResult )
1193 pOutMessageNumberOrResult[pSort->m_idx] = result;
1194 }
1195
1196 // Flush out last connection, if any
1197 if ( bConnectionThinkImmediately )
1198 pConn->CheckConnectionStateOrScheduleWakeUp( usecNow );
1199 }
1200
FlushMessagesOnConnection(HSteamNetConnection hConn)1201 EResult CSteamNetworkingSockets::FlushMessagesOnConnection( HSteamNetConnection hConn )
1202 {
1203 //SteamNetworkingGlobalLock scopeLock( "FlushMessagesOnConnection" ); // NO, not necessary!
1204 ConnectionScopeLock connectionLock;
1205 CSteamNetworkConnectionBase *pConn = GetConnectionByHandleForAPI( hConn, connectionLock, "FlushMessagesOnConnection" );
1206 if ( !pConn )
1207 return k_EResultInvalidParam;
1208 return pConn->APIFlushMessageOnConnection();
1209 }
1210
ReceiveMessagesOnConnection(HSteamNetConnection hConn,SteamNetworkingMessage_t ** ppOutMessages,int nMaxMessages)1211 int CSteamNetworkingSockets::ReceiveMessagesOnConnection( HSteamNetConnection hConn, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages )
1212 {
1213 //SteamNetworkingGlobalLock scopeLock( "ReceiveMessagesOnConnection" ); // NO, not necessary!
1214 ConnectionScopeLock connectionLock;
1215 CSteamNetworkConnectionBase *pConn = GetConnectionByHandleForAPI( hConn, connectionLock, "ReceiveMessagesOnConnection" );
1216 if ( !pConn )
1217 return -1;
1218 return pConn->APIReceiveMessages( ppOutMessages, nMaxMessages );
1219 }
1220
CreatePollGroup()1221 HSteamNetPollGroup CSteamNetworkingSockets::CreatePollGroup()
1222 {
1223 SteamNetworkingGlobalLock scopeLock( "CreatePollGroup" ); // Take global lock, because we will be creating objects
1224 PollGroupScopeLock pollGroupScopeLock;
1225 CSteamNetworkPollGroup *pPollGroup = InternalCreatePollGroup( pollGroupScopeLock );
1226 return pPollGroup->m_hPollGroupSelf;
1227 }
1228
InternalCreatePollGroup(PollGroupScopeLock & scopeLock)1229 CSteamNetworkPollGroup *CSteamNetworkingSockets::InternalCreatePollGroup( PollGroupScopeLock &scopeLock )
1230 {
1231 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
1232 TableScopeLock tableScopeLock( g_tables_lock );
1233 CSteamNetworkPollGroup *pPollGroup = new CSteamNetworkPollGroup( this );
1234 scopeLock.Lock( pPollGroup->m_lock );
1235 pPollGroup->AssignHandleAndAddToGlobalTable();
1236 return pPollGroup;
1237 }
1238
DestroyPollGroup(HSteamNetPollGroup hPollGroup)1239 bool CSteamNetworkingSockets::DestroyPollGroup( HSteamNetPollGroup hPollGroup )
1240 {
1241 SteamNetworkingGlobalLock scopeLock( "DestroyPollGroup" ); // Take global lock, since we'll be destroying objects
1242 TableScopeLock tableScopeLock( g_tables_lock ); // We'll need to be able to remove the poll group from the tables list
1243 PollGroupScopeLock pollGroupLock;
1244 CSteamNetworkPollGroup *pPollGroup = GetPollGroupByHandle( hPollGroup, pollGroupLock, nullptr );
1245 if ( !pPollGroup )
1246 return false;
1247 pollGroupLock.Abandon(); // We're about to destroy the lock itself. The Destructor will unlock -- we don't want to do it again.
1248 delete pPollGroup;
1249 return true;
1250 }
1251
SetConnectionPollGroup(HSteamNetConnection hConn,HSteamNetPollGroup hPollGroup)1252 bool CSteamNetworkingSockets::SetConnectionPollGroup( HSteamNetConnection hConn, HSteamNetPollGroup hPollGroup )
1253 {
1254 SteamNetworkingGlobalLock scopeLock( "SetConnectionPollGroup" ); // Take global lock, since we'll need to take multiple object locks
1255 ConnectionScopeLock connectionLock;
1256 CSteamNetworkConnectionBase *pConn = GetConnectionByHandleForAPI( hConn, connectionLock, nullptr );
1257 if ( !pConn )
1258 return false;
1259
1260 // NOTE: We are allowed to take multiple locks here, in any order, because we have the global
1261 // lock. Code that does not hold the global lock may only lock one object at a time
1262
1263 // Special case for removing the poll group
1264 if ( hPollGroup == k_HSteamNetPollGroup_Invalid )
1265 {
1266 pConn->RemoveFromPollGroup();
1267 return true;
1268 }
1269
1270
1271 PollGroupScopeLock pollGroupLock;
1272 CSteamNetworkPollGroup *pPollGroup = GetPollGroupByHandle( hPollGroup, pollGroupLock, nullptr );
1273 if ( !pPollGroup )
1274 return false;
1275
1276 pConn->SetPollGroup( pPollGroup );
1277
1278 return true;
1279 }
1280
ReceiveMessagesOnPollGroup(HSteamNetPollGroup hPollGroup,SteamNetworkingMessage_t ** ppOutMessages,int nMaxMessages)1281 int CSteamNetworkingSockets::ReceiveMessagesOnPollGroup( HSteamNetPollGroup hPollGroup, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages )
1282 {
1283 //SteamNetworkingGlobalLock scopeLock( "ReceiveMessagesOnPollGroup" ); // NO, not necessary!
1284 PollGroupScopeLock pollGroupLock;
1285 CSteamNetworkPollGroup *pPollGroup = GetPollGroupByHandle( hPollGroup, pollGroupLock, "ReceiveMessagesOnPollGroup" );
1286 if ( !pPollGroup )
1287 return -1;
1288 g_lockAllRecvMessageQueues.lock();
1289 int nMessagesReceived = pPollGroup->m_queueRecvMessages.RemoveMessages( ppOutMessages, nMaxMessages );
1290 g_lockAllRecvMessageQueues.unlock();
1291 return nMessagesReceived;
1292 }
1293
1294 #ifdef STEAMNETWORKINGSOCKETS_STEAMCLIENT
ReceiveMessagesOnListenSocketLegacyPollGroup(HSteamListenSocket hSocket,SteamNetworkingMessage_t ** ppOutMessages,int nMaxMessages)1295 int CSteamNetworkingSockets::ReceiveMessagesOnListenSocketLegacyPollGroup( HSteamListenSocket hSocket, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages )
1296 {
1297 SteamNetworkingGlobalLock scopeLock( "ReceiveMessagesOnListenSocket" );
1298 CSteamNetworkListenSocketBase *pSock = GetListenSocketByHandle( hSocket );
1299 if ( !pSock )
1300 return -1;
1301 if ( !pSock->m_pLegacyPollGroup )
1302 return 0;
1303 g_lockAllRecvMessageQueues.lock();
1304 int nMessagesReceived = pSock->m_pLegacyPollGroup->m_queueRecvMessages.RemoveMessages( ppOutMessages, nMaxMessages );
1305 g_lockAllRecvMessageQueues.unlock();
1306 return nMessagesReceived;
1307 }
1308 #endif
1309
GetConnectionInfo(HSteamNetConnection hConn,SteamNetConnectionInfo_t * pInfo)1310 bool CSteamNetworkingSockets::GetConnectionInfo( HSteamNetConnection hConn, SteamNetConnectionInfo_t *pInfo )
1311 {
1312 //SteamNetworkingGlobalLock scopeLock( "GetConnectionInfo" ); // NO, not necessary!
1313 ConnectionScopeLock connectionLock;
1314 CSteamNetworkConnectionBase *pConn = GetConnectionByHandleForAPI( hConn, connectionLock, "GetConnectionInfo" );
1315 if ( !pConn )
1316 return false;
1317 if ( pInfo )
1318 pConn->ConnectionPopulateInfo( *pInfo );
1319 return true;
1320 }
1321
GetQuickConnectionStatus(HSteamNetConnection hConn,SteamNetworkingQuickConnectionStatus * pStats)1322 bool CSteamNetworkingSockets::GetQuickConnectionStatus( HSteamNetConnection hConn, SteamNetworkingQuickConnectionStatus *pStats )
1323 {
1324 //SteamNetworkingGlobalLock scopeLock( "GetQuickConnectionStatus" ); // NO, not necessary!
1325 ConnectionScopeLock connectionLock;
1326 CSteamNetworkConnectionBase *pConn = GetConnectionByHandleForAPI( hConn, connectionLock, "GetQuickConnectionStatus" );
1327 if ( !pConn )
1328 return false;
1329 if ( pStats )
1330 pConn->APIGetQuickConnectionStatus( *pStats );
1331 return true;
1332 }
1333
GetDetailedConnectionStatus(HSteamNetConnection hConn,char * pszBuf,int cbBuf)1334 int CSteamNetworkingSockets::GetDetailedConnectionStatus( HSteamNetConnection hConn, char *pszBuf, int cbBuf )
1335 {
1336 SteamNetworkingDetailedConnectionStatus stats;
1337
1338 // Only hold the lock for as long as we need.
1339 {
1340 SteamNetworkingGlobalLock scopeLock( "GetDetailedConnectionStatus" ); // In some use cases (SDR), we need to touch some shared data structures. It's easier to just protect this with the global lock than to try to sort that out
1341 ConnectionScopeLock connectionLock;
1342 CSteamNetworkConnectionBase *pConn = GetConnectionByHandleForAPI( hConn, connectionLock, nullptr );
1343 if ( !pConn )
1344 return -1;
1345
1346 pConn->APIGetDetailedConnectionStatus( stats, SteamNetworkingSockets_GetLocalTimestamp() );
1347
1348 } // Release lock. We don't need it, and printing can take a while!
1349 int r = stats.Print( pszBuf, cbBuf );
1350
1351 /// If just asking for buffer size, pad it a bunch
1352 /// because connection status can change at any moment.
1353 if ( r > 0 )
1354 r += 1024;
1355 return r;
1356 }
1357
GetListenSocketAddress(HSteamListenSocket hSocket,SteamNetworkingIPAddr * pAddress)1358 bool CSteamNetworkingSockets::GetListenSocketAddress( HSteamListenSocket hSocket, SteamNetworkingIPAddr *pAddress )
1359 {
1360 SteamNetworkingGlobalLock scopeLock( "GetListenSocketAddress" );
1361 CSteamNetworkListenSocketBase *pSock = GetListenSocketByHandle( hSocket );
1362 if ( !pSock )
1363 return false;
1364 return pSock->APIGetAddress( pAddress );
1365 }
1366
CreateSocketPair(HSteamNetConnection * pOutConnection1,HSteamNetConnection * pOutConnection2,bool bUseNetworkLoopback,const SteamNetworkingIdentity * pIdentity1,const SteamNetworkingIdentity * pIdentity2)1367 bool CSteamNetworkingSockets::CreateSocketPair( HSteamNetConnection *pOutConnection1, HSteamNetConnection *pOutConnection2, bool bUseNetworkLoopback, const SteamNetworkingIdentity *pIdentity1, const SteamNetworkingIdentity *pIdentity2 )
1368 {
1369 SteamNetworkingGlobalLock scopeLock( "CreateSocketPair" );
1370
1371 // Assume failure
1372 *pOutConnection1 = k_HSteamNetConnection_Invalid;
1373 *pOutConnection2 = k_HSteamNetConnection_Invalid;
1374 SteamNetworkingIdentity identity[2];
1375 if ( pIdentity1 )
1376 identity[0] = *pIdentity1;
1377 else
1378 identity[0].SetLocalHost();
1379 if ( pIdentity2 )
1380 identity[1] = *pIdentity2;
1381 else
1382 identity[1].SetLocalHost();
1383
1384 // Create network connections?
1385 if ( bUseNetworkLoopback )
1386 {
1387 // Create two connection objects
1388 CSteamNetworkConnectionlocalhostLoopback *pConn[2];
1389 if ( !CSteamNetworkConnectionlocalhostLoopback::APICreateSocketPair( this, pConn, identity ) )
1390 return false;
1391
1392 // Return their handles
1393 *pOutConnection1 = pConn[0]->m_hConnectionSelf;
1394 *pOutConnection2 = pConn[1]->m_hConnectionSelf;
1395 }
1396 else
1397 {
1398 // Create two connection objects
1399 CSteamNetworkConnectionPipe *pConn[2];
1400 if ( !CSteamNetworkConnectionPipe::APICreateSocketPair( this, pConn, identity ) )
1401 return false;
1402
1403 // Return their handles
1404 *pOutConnection1 = pConn[0]->m_hConnectionSelf;
1405 *pOutConnection2 = pConn[1]->m_hConnectionSelf;
1406 }
1407 return true;
1408 }
1409
BCertHasIdentity() const1410 bool CSteamNetworkingSockets::BCertHasIdentity() const
1411 {
1412 // We should actually have a cert, otherwise this question cannot be answered
1413 Assert( m_msgSignedCert.has_cert() );
1414 Assert( m_msgCert.has_key_data() );
1415 return m_msgCert.has_identity_string() || m_msgCert.has_legacy_identity_binary() || m_msgCert.has_legacy_steam_id();
1416 }
1417
1418
SetCertificateAndPrivateKey(const void * pCert,int cbCert,void * pPrivateKey,int cbPrivateKey)1419 bool CSteamNetworkingSockets::SetCertificateAndPrivateKey( const void *pCert, int cbCert, void *pPrivateKey, int cbPrivateKey )
1420 {
1421 SteamNetworkingGlobalLock::AssertHeldByCurrentThread( "SetCertificateAndPrivateKey" );
1422
1423 m_msgCert.Clear();
1424 m_msgSignedCert.Clear();
1425 m_keyPrivateKey.Wipe();
1426
1427 //
1428 // Decode the private key
1429 //
1430 if ( !m_keyPrivateKey.LoadFromAndWipeBuffer( pPrivateKey, cbPrivateKey ) )
1431 {
1432 SetCertStatus( k_ESteamNetworkingAvailability_Failed, "Invalid private key" );
1433 return false;
1434 }
1435
1436 //
1437 // Decode the cert
1438 //
1439 SteamNetworkingErrMsg parseErrMsg;
1440 if ( !ParseCertFromPEM( pCert, cbCert, m_msgSignedCert, parseErrMsg ) )
1441 {
1442 SetCertStatus( k_ESteamNetworkingAvailability_Failed, parseErrMsg );
1443 return false;
1444 }
1445
1446 if (
1447 !m_msgSignedCert.has_cert()
1448 || !m_msgCert.ParseFromString( m_msgSignedCert.cert() )
1449 || !m_msgCert.has_time_expiry()
1450 || !m_msgCert.has_key_data()
1451 ) {
1452 SetCertStatus( k_ESteamNetworkingAvailability_Failed, "Invalid cert" );
1453 return false;
1454 }
1455 if ( m_msgCert.key_type() != CMsgSteamDatagramCertificate_EKeyType_ED25519 )
1456 {
1457 SetCertStatus( k_ESteamNetworkingAvailability_Failed, "Invalid cert or unsupported public key type" );
1458 return false;
1459 }
1460
1461 //
1462 // Make sure that the private key and the cert match!
1463 //
1464
1465 CECSigningPublicKey pubKey;
1466 if ( !pubKey.SetRawDataWithoutWipingInput( m_msgCert.key_data().c_str(), m_msgCert.key_data().length() ) )
1467 {
1468 SetCertStatus( k_ESteamNetworkingAvailability_Failed, "Invalid public key" );
1469 return false;
1470 }
1471 if ( !m_keyPrivateKey.MatchesPublicKey( pubKey ) )
1472 {
1473 SetCertStatus( k_ESteamNetworkingAvailability_Failed, "Private key doesn't match public key from cert" );
1474 return false;
1475 }
1476
1477 SetCertStatus( k_ESteamNetworkingAvailability_Current, "OK" );
1478
1479 return true;
1480 }
1481
GetP2P_Transport_ICE_Enable(const SteamNetworkingIdentity & identityRemote,int * pOutUserFlags)1482 int CSteamNetworkingSockets::GetP2P_Transport_ICE_Enable( const SteamNetworkingIdentity &identityRemote, int *pOutUserFlags )
1483 {
1484 // We really shouldn't get here, because this is only a question that makes sense
1485 // to ask if we have also overridden this function in a derived class, or slammed
1486 // it before making the connection
1487 Assert( false );
1488 if ( pOutUserFlags )
1489 *pOutUserFlags = 0;
1490 return k_nSteamNetworkingConfig_P2P_Transport_ICE_Enable_Disable;
1491 }
1492
RunCallbacks()1493 void CSteamNetworkingSockets::RunCallbacks()
1494 {
1495
1496 // Swap into a temp, so that we only hold lock for
1497 // a brief period.
1498 std_vector<QueuedCallback> listTemp;
1499 m_mutexPendingCallbacks.lock();
1500 listTemp.swap( m_vecPendingCallbacks );
1501 m_mutexPendingCallbacks.unlock();
1502
1503 // Dispatch the callbacks
1504 for ( QueuedCallback &x: listTemp )
1505 {
1506 // NOTE: this switch statement is probably not necessary, if we are willing to make
1507 // some (almost certainly reasonable in practice) assumptions about the parameter
1508 // passing ABI. All of these function calls basically have the same signature except
1509 // for the actual type of the argument being pointed to.
1510
1511 #define DISPATCH_CALLBACK( structType, fnType ) \
1512 case structType::k_iCallback: \
1513 COMPILE_TIME_ASSERT( sizeof(structType) <= sizeof(x.data) ); \
1514 ((fnType)x.fnCallback)( (structType*)x.data ); \
1515 break; \
1516
1517 switch ( x.nCallback )
1518 {
1519 DISPATCH_CALLBACK( SteamNetConnectionStatusChangedCallback_t, FnSteamNetConnectionStatusChanged )
1520 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
1521 DISPATCH_CALLBACK( SteamNetAuthenticationStatus_t, FnSteamNetAuthenticationStatusChanged )
1522 DISPATCH_CALLBACK( SteamRelayNetworkStatus_t, FnSteamRelayNetworkStatusChanged )
1523 #endif
1524 #ifdef STEAMNETWORKINGSOCKETS_ENABLE_STEAMNETWORKINGMESSAGES
1525 DISPATCH_CALLBACK( SteamNetworkingMessagesSessionRequest_t, FnSteamNetworkingMessagesSessionRequest )
1526 DISPATCH_CALLBACK( SteamNetworkingMessagesSessionFailed_t, FnSteamNetworkingMessagesSessionFailed )
1527 #endif
1528 default:
1529 AssertMsg1( false, "Unknown callback type %d!", x.nCallback );
1530 }
1531
1532 #undef DISPATCH_CALLBACK
1533 }
1534 }
1535
InternalQueueCallback(int nCallback,int cbCallback,const void * pvCallback,void * fnRegisteredFunctionPtr)1536 void CSteamNetworkingSockets::InternalQueueCallback( int nCallback, int cbCallback, const void *pvCallback, void *fnRegisteredFunctionPtr )
1537 {
1538 SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
1539
1540 if ( !fnRegisteredFunctionPtr )
1541 return;
1542 if ( cbCallback > sizeof( ((QueuedCallback*)0)->data ) )
1543 {
1544 AssertMsg( false, "Callback doesn't fit!" );
1545 return;
1546 }
1547 AssertMsg( len( m_vecPendingCallbacks ) < 100, "Callbacks backing up and not being checked. Need to check them more frequently!" );
1548
1549 m_mutexPendingCallbacks.lock();
1550 QueuedCallback &q = *push_back_get_ptr( m_vecPendingCallbacks );
1551 q.nCallback = nCallback;
1552 q.fnCallback = fnRegisteredFunctionPtr;
1553 memcpy( q.data, pvCallback, cbCallback );
1554 m_mutexPendingCallbacks.unlock();
1555 }
1556
1557 /////////////////////////////////////////////////////////////////////////////
1558 //
1559 // CSteamNetworkingUtils
1560 //
1561 /////////////////////////////////////////////////////////////////////////////
1562
~CSteamNetworkingUtils()1563 CSteamNetworkingUtils::~CSteamNetworkingUtils() {}
1564
AllocateMessage(int cbAllocateBuffer)1565 SteamNetworkingMessage_t *CSteamNetworkingUtils::AllocateMessage( int cbAllocateBuffer )
1566 {
1567 return CSteamNetworkingMessage::New( cbAllocateBuffer );
1568 }
1569
GetLocalTimestamp()1570 SteamNetworkingMicroseconds CSteamNetworkingUtils::GetLocalTimestamp()
1571 {
1572 return SteamNetworkingSockets_GetLocalTimestamp();
1573 }
1574
SetDebugOutputFunction(ESteamNetworkingSocketsDebugOutputType eDetailLevel,FSteamNetworkingSocketsDebugOutput pfnFunc)1575 void CSteamNetworkingUtils::SetDebugOutputFunction( ESteamNetworkingSocketsDebugOutputType eDetailLevel, FSteamNetworkingSocketsDebugOutput pfnFunc )
1576 {
1577 SteamNetworkingSockets_SetDebugOutputFunction( eDetailLevel, pfnFunc );
1578 }
1579
1580
1581 template<typename T>
GetConnectionVar(const GlobalConfigValueEntry * pEntry,ConnectionConfig * pConnectionConfig)1582 static ConfigValue<T> *GetConnectionVar( const GlobalConfigValueEntry *pEntry, ConnectionConfig *pConnectionConfig )
1583 {
1584 Assert( pEntry->m_eScope == k_ESteamNetworkingConfig_Connection );
1585 intptr_t ptr = intptr_t( pConnectionConfig );
1586 return (ConfigValue<T> *)( ptr + pEntry->m_cbOffsetOf );
1587 }
1588
1589 template<typename T>
EvaluateScopeConfigValue(GlobalConfigValueEntry * pEntry,ESteamNetworkingConfigScope eScopeType,intptr_t scopeObj,ConnectionScopeLock & connectionLock)1590 static ConfigValue<T> *EvaluateScopeConfigValue( GlobalConfigValueEntry *pEntry,
1591 ESteamNetworkingConfigScope eScopeType,
1592 intptr_t scopeObj,
1593 ConnectionScopeLock &connectionLock // Lock this, if it's a connection
1594 )
1595 {
1596 switch ( eScopeType )
1597 {
1598 case k_ESteamNetworkingConfig_Global:
1599 {
1600 auto *pGlobalVal = static_cast< GlobalConfigValueBase<T> * >( pEntry );
1601 return &pGlobalVal->m_value;
1602 }
1603
1604 case k_ESteamNetworkingConfig_SocketsInterface:
1605 {
1606 CSteamNetworkingSockets *pInterface = (CSteamNetworkingSockets *)scopeObj;
1607 if ( pEntry->m_eScope == k_ESteamNetworkingConfig_Connection )
1608 {
1609 return GetConnectionVar<T>( pEntry, &pInterface->m_connectionConfig );
1610 }
1611 break;
1612 }
1613
1614 case k_ESteamNetworkingConfig_ListenSocket:
1615 {
1616 CSteamNetworkListenSocketBase *pSock = GetListenSocketByHandle( HSteamListenSocket( scopeObj ) );
1617 if ( pSock )
1618 {
1619 if ( pEntry->m_eScope == k_ESteamNetworkingConfig_Connection )
1620 {
1621 return GetConnectionVar<T>( pEntry, &pSock->m_connectionConfig );
1622 }
1623 }
1624 break;
1625 }
1626
1627 case k_ESteamNetworkingConfig_Connection:
1628 {
1629 // NOTE: Not using GetConnectionByHandleForAPI here. In a few places in the code,
1630 // we need to be able to set config options for connections that are being created.
1631 // Really, we ought to plumb through these calls to an internal interface, so that
1632 // we would know that they should be given access. Right now they are coming in
1633 // the "front door". So this means if the app tries to set a config option on a
1634 // connection that technically no longer exists, we will actually allow that, when
1635 // we probably should fail the call.
1636 CSteamNetworkConnectionBase *pConn = GetConnectionByHandle( HSteamNetConnection( scopeObj ), connectionLock );
1637 if ( pConn )
1638 {
1639 if ( pEntry->m_eScope == k_ESteamNetworkingConfig_Connection )
1640 {
1641 return GetConnectionVar<T>( pEntry, &pConn->m_connectionConfig );
1642 }
1643 }
1644 break;
1645 }
1646
1647 }
1648
1649 // Bad scope argument
1650 return nullptr;
1651 }
1652
AssignConfigValueTyped(int32 * pVal,ESteamNetworkingConfigDataType eDataType,const void * pArg)1653 static bool AssignConfigValueTyped( int32 *pVal, ESteamNetworkingConfigDataType eDataType, const void *pArg )
1654 {
1655 switch ( eDataType )
1656 {
1657 case k_ESteamNetworkingConfig_Int32:
1658 *pVal = *(int32*)pArg;
1659 break;
1660
1661 case k_ESteamNetworkingConfig_Int64:
1662 {
1663 int64 arg = *(int64*)pArg;
1664 if ( (int32)arg != arg )
1665 return false; // Cannot truncate!
1666 *pVal = *(int32*)arg;
1667 break;
1668 }
1669
1670 case k_ESteamNetworkingConfig_Float:
1671 *pVal = (int32)floor( *(float*)pArg + .5f );
1672 break;
1673
1674 case k_ESteamNetworkingConfig_String:
1675 {
1676 int x;
1677 if ( sscanf( (const char *)pArg, "%d", &x ) != 1 )
1678 return false;
1679 *pVal = x;
1680 break;
1681 }
1682
1683 default:
1684 return false;
1685 }
1686
1687 return true;
1688 }
1689
AssignConfigValueTyped(int64 * pVal,ESteamNetworkingConfigDataType eDataType,const void * pArg)1690 static bool AssignConfigValueTyped( int64 *pVal, ESteamNetworkingConfigDataType eDataType, const void *pArg )
1691 {
1692 switch ( eDataType )
1693 {
1694 case k_ESteamNetworkingConfig_Int32:
1695 *pVal = *(int32*)pArg;
1696 break;
1697
1698 case k_ESteamNetworkingConfig_Int64:
1699 {
1700 *pVal = *(int64*)pArg;
1701 break;
1702 }
1703
1704 case k_ESteamNetworkingConfig_Float:
1705 *pVal = (int64)floor( *(float*)pArg + .5f );
1706 break;
1707
1708 case k_ESteamNetworkingConfig_String:
1709 {
1710 long long x;
1711 if ( sscanf( (const char *)pArg, "%lld", &x ) != 1 )
1712 return false;
1713 *pVal = (int64)x;
1714 break;
1715 }
1716
1717 default:
1718 return false;
1719 }
1720
1721 return true;
1722 }
1723
AssignConfigValueTyped(float * pVal,ESteamNetworkingConfigDataType eDataType,const void * pArg)1724 static bool AssignConfigValueTyped( float *pVal, ESteamNetworkingConfigDataType eDataType, const void *pArg )
1725 {
1726 switch ( eDataType )
1727 {
1728 case k_ESteamNetworkingConfig_Int32:
1729 *pVal = (float)( *(int32*)pArg );
1730 break;
1731
1732 case k_ESteamNetworkingConfig_Int64:
1733 {
1734 *pVal = (float)( *(int64*)pArg );
1735 break;
1736 }
1737
1738 case k_ESteamNetworkingConfig_Float:
1739 *pVal = *(float*)pArg;
1740 break;
1741
1742 case k_ESteamNetworkingConfig_String:
1743 {
1744 float x;
1745 if ( sscanf( (const char *)pArg, "%f", &x ) != 1 )
1746 return false;
1747 *pVal = x;
1748 break;
1749 }
1750
1751 default:
1752 return false;
1753 }
1754
1755 return true;
1756 }
1757
AssignConfigValueTyped(std::string * pVal,ESteamNetworkingConfigDataType eDataType,const void * pArg)1758 static bool AssignConfigValueTyped( std::string *pVal, ESteamNetworkingConfigDataType eDataType, const void *pArg )
1759 {
1760 char temp[64];
1761
1762 switch ( eDataType )
1763 {
1764 case k_ESteamNetworkingConfig_Int32:
1765 V_sprintf_safe( temp, "%d", *(int32*)pArg );
1766 *pVal = temp;
1767 break;
1768
1769 case k_ESteamNetworkingConfig_Int64:
1770 V_sprintf_safe( temp, "%lld", (long long)*(int64*)pArg );
1771 *pVal = temp;
1772 break;
1773
1774 case k_ESteamNetworkingConfig_Float:
1775 V_sprintf_safe( temp, "%g", *(float*)pArg );
1776 *pVal = temp;
1777 break;
1778
1779 case k_ESteamNetworkingConfig_String:
1780 *pVal = (const char *)pArg;
1781 break;
1782
1783 default:
1784 return false;
1785 }
1786
1787 return true;
1788 }
1789
AssignConfigValueTyped(void ** pVal,ESteamNetworkingConfigDataType eDataType,const void * pArg)1790 static bool AssignConfigValueTyped( void **pVal, ESteamNetworkingConfigDataType eDataType, const void *pArg )
1791 {
1792 switch ( eDataType )
1793 {
1794 case k_ESteamNetworkingConfig_Ptr:
1795 *pVal = *(void **)pArg;
1796 break;
1797
1798 default:
1799 return false;
1800 }
1801
1802 return true;
1803 }
1804
1805 template<typename T>
SetConfigValueTyped(GlobalConfigValueEntry * pEntry,ESteamNetworkingConfigScope eScopeType,intptr_t scopeObj,ESteamNetworkingConfigDataType eDataType,const void * pArg)1806 bool SetConfigValueTyped(
1807 GlobalConfigValueEntry *pEntry,
1808 ESteamNetworkingConfigScope eScopeType,
1809 intptr_t scopeObj,
1810 ESteamNetworkingConfigDataType eDataType,
1811 const void *pArg
1812 ) {
1813 ConnectionScopeLock connectionLock;
1814 ConfigValue<T> *pVal = EvaluateScopeConfigValue<T>( pEntry, eScopeType, scopeObj, connectionLock );
1815 if ( !pVal )
1816 return false;
1817
1818 // Locked values cannot be changed
1819 if ( pVal->IsLocked() )
1820 return false;
1821
1822 // Clearing the value?
1823 if ( pArg == nullptr )
1824 {
1825 if ( eScopeType == k_ESteamNetworkingConfig_Global )
1826 {
1827 auto *pGlobal = (typename GlobalConfigValueBase<T>::Value *)( pVal );
1828 Assert( pGlobal->m_pInherit == nullptr );
1829 Assert( pGlobal->IsSet() );
1830 pGlobal->m_data = pGlobal->m_defaultValue;
1831 }
1832 else if ( eScopeType == k_ESteamNetworkingConfig_Connection && pEntry->m_eValue == k_ESteamNetworkingConfig_ConnectionUserData )
1833 {
1834 // Once this is set, we cannot clear it or inherit it.
1835 SpewError( "Cannot clear connection user data\n" );
1836 return false;
1837 }
1838 else
1839 {
1840 Assert( pVal->m_pInherit );
1841 pVal->m_eState = ConfigValueBase::kENotSet;
1842 }
1843 return true;
1844 }
1845
1846 // Call type-specific method to set it
1847 if ( !AssignConfigValueTyped( &pVal->m_data, eDataType, pArg ) )
1848 return false;
1849
1850 // Mark it as set
1851 pVal->m_eState = ConfigValueBase::kESet;
1852
1853 // Apply limits
1854 pEntry->Clamp<T>( pVal->m_data );
1855
1856 // OK
1857 return true;
1858 }
1859
1860 template<typename T>
ReturnConfigValueTyped(const T & data,void * pData,size_t * cbData)1861 ESteamNetworkingGetConfigValueResult ReturnConfigValueTyped( const T &data, void *pData, size_t *cbData )
1862 {
1863 ESteamNetworkingGetConfigValueResult eResult;
1864 if ( !pData || *cbData < sizeof(T) )
1865 {
1866 eResult = k_ESteamNetworkingGetConfigValue_BufferTooSmall;
1867 }
1868 else
1869 {
1870 *(T*)pData = data;
1871 eResult = k_ESteamNetworkingGetConfigValue_OK;
1872 }
1873 *cbData = sizeof(T);
1874 return eResult;
1875 }
1876
1877 template<>
ReturnConfigValueTyped(const std::string & data,void * pData,size_t * cbData)1878 ESteamNetworkingGetConfigValueResult ReturnConfigValueTyped<std::string>( const std::string &data, void *pData, size_t *cbData )
1879 {
1880 size_t l = data.length() + 1;
1881 ESteamNetworkingGetConfigValueResult eResult;
1882 if ( !pData || *cbData < l )
1883 {
1884 eResult = k_ESteamNetworkingGetConfigValue_BufferTooSmall;
1885 }
1886 else
1887 {
1888 memcpy( pData, data.c_str(), l );
1889 eResult = k_ESteamNetworkingGetConfigValue_OK;
1890 }
1891 *cbData = l;
1892 return eResult;
1893 }
1894
1895 template<typename T>
GetConfigValueTyped(GlobalConfigValueEntry * pEntry,ESteamNetworkingConfigScope eScopeType,intptr_t scopeObj,void * pResult,size_t * cbResult)1896 ESteamNetworkingGetConfigValueResult GetConfigValueTyped(
1897 GlobalConfigValueEntry *pEntry,
1898 ESteamNetworkingConfigScope eScopeType,
1899 intptr_t scopeObj,
1900 void *pResult, size_t *cbResult
1901 ) {
1902 ConnectionScopeLock connectionLock;
1903 ConfigValue<T> *pVal = EvaluateScopeConfigValue<T>( pEntry, eScopeType, scopeObj, connectionLock );
1904 if ( !pVal )
1905 {
1906 *cbResult = 0;
1907 return k_ESteamNetworkingGetConfigValue_BadScopeObj;
1908 }
1909
1910 // Remember if it was set at this level
1911 bool bValWasSet = pVal->IsSet();
1912
1913 // Find the place where the actual value comes from
1914 while ( !pVal->IsSet() )
1915 {
1916 Assert( pVal->m_pInherit );
1917 pVal = static_cast<ConfigValue<T> *>( pVal->m_pInherit );
1918 }
1919
1920 // Call type-specific method to return it
1921 ESteamNetworkingGetConfigValueResult eResult = ReturnConfigValueTyped( pVal->m_data, pResult, cbResult );
1922 if ( eResult == k_ESteamNetworkingGetConfigValue_OK && !bValWasSet )
1923 eResult = k_ESteamNetworkingGetConfigValue_OKInherited;
1924 return eResult;
1925 }
1926
SetConfigValue(ESteamNetworkingConfigValue eValue,ESteamNetworkingConfigScope eScopeType,intptr_t scopeObj,ESteamNetworkingConfigDataType eDataType,const void * pValue)1927 bool CSteamNetworkingUtils::SetConfigValue( ESteamNetworkingConfigValue eValue,
1928 ESteamNetworkingConfigScope eScopeType, intptr_t scopeObj,
1929 ESteamNetworkingConfigDataType eDataType, const void *pValue )
1930 {
1931
1932 // Check for special values
1933 switch ( eValue )
1934 {
1935 case k_ESteamNetworkingConfig_MTU_DataSize:
1936 SpewWarning( "MTU_DataSize is readonly" );
1937 return false;
1938
1939 case k_ESteamNetworkingConfig_ConnectionUserData:
1940 {
1941
1942 // We only need special handling when modifying a connection
1943 if ( eScopeType != k_ESteamNetworkingConfig_Connection )
1944 break;
1945
1946 // Process the user argument, maybe performing type conversion
1947 int64 newData;
1948 if ( !AssignConfigValueTyped( &newData, eDataType, pValue ) )
1949 return false;
1950
1951 // Lookup the connection
1952 ConnectionScopeLock connectionLock;
1953 CSteamNetworkConnectionBase *pConn = GetConnectionByHandle( HSteamNetConnection( scopeObj ), connectionLock );
1954 if ( !pConn )
1955 return false;
1956
1957 // Set the data, possibly fixing up existing queued messages, etc
1958 pConn->SetUserData( pConn->m_connectionConfig.m_ConnectionUserData.m_data );
1959 return true;
1960 }
1961
1962 }
1963
1964 GlobalConfigValueEntry *pEntry = FindConfigValueEntry( eValue );
1965 if ( pEntry == nullptr )
1966 return false;
1967
1968 SteamNetworkingGlobalLock scopeLock( "SetConfigValue" );
1969
1970 switch ( pEntry->m_eDataType )
1971 {
1972 case k_ESteamNetworkingConfig_Int32: return SetConfigValueTyped<int32>( pEntry, eScopeType, scopeObj, eDataType, pValue );
1973 case k_ESteamNetworkingConfig_Int64: return SetConfigValueTyped<int64>( pEntry, eScopeType, scopeObj, eDataType, pValue );
1974 case k_ESteamNetworkingConfig_Float: return SetConfigValueTyped<float>( pEntry, eScopeType, scopeObj, eDataType, pValue );
1975 case k_ESteamNetworkingConfig_String: return SetConfigValueTyped<std::string>( pEntry, eScopeType, scopeObj, eDataType, pValue );
1976 case k_ESteamNetworkingConfig_Ptr: return SetConfigValueTyped<void *>( pEntry, eScopeType, scopeObj, eDataType, pValue );
1977 }
1978
1979 Assert( false );
1980 return false;
1981 }
1982
GetConfigValue(ESteamNetworkingConfigValue eValue,ESteamNetworkingConfigScope eScopeType,intptr_t scopeObj,ESteamNetworkingConfigDataType * pOutDataType,void * pResult,size_t * cbResult)1983 ESteamNetworkingGetConfigValueResult CSteamNetworkingUtils::GetConfigValue(
1984 ESteamNetworkingConfigValue eValue, ESteamNetworkingConfigScope eScopeType,
1985 intptr_t scopeObj, ESteamNetworkingConfigDataType *pOutDataType,
1986 void *pResult, size_t *cbResult )
1987 {
1988 // Take the global lock.
1989 SteamNetworkingGlobalLock scopeLock( "GetConfigValue" );
1990
1991 if ( eValue == k_ESteamNetworkingConfig_MTU_DataSize )
1992 {
1993 int32 MTU_packetsize;
1994 size_t cbMTU_packetsize = sizeof(MTU_packetsize);
1995 ESteamNetworkingGetConfigValueResult rFetch = GetConfigValueTyped<int32>( &g_ConfigDefault_MTU_PacketSize, eScopeType, scopeObj, &MTU_packetsize, &cbMTU_packetsize );
1996 if ( rFetch < 0 )
1997 return rFetch;
1998
1999 int32 MTU_DataSize = std::max( 0, MTU_packetsize - k_cbSteamNetworkingSocketsNoFragmentHeaderReserve );
2000 ESteamNetworkingGetConfigValueResult rStore = ReturnConfigValueTyped<int32>( MTU_DataSize, pResult, cbResult );
2001 if ( rStore != k_ESteamNetworkingGetConfigValue_OK )
2002 return rStore;
2003 return rFetch;
2004 }
2005
2006 GlobalConfigValueEntry *pEntry = FindConfigValueEntry( eValue );
2007 if ( pEntry == nullptr )
2008 return k_ESteamNetworkingGetConfigValue_BadValue;
2009
2010 if ( pOutDataType )
2011 *pOutDataType = pEntry->m_eDataType;
2012
2013 switch ( pEntry->m_eDataType )
2014 {
2015 case k_ESteamNetworkingConfig_Int32: return GetConfigValueTyped<int32>( pEntry, eScopeType, scopeObj, pResult, cbResult );
2016 case k_ESteamNetworkingConfig_Int64: return GetConfigValueTyped<int64>( pEntry, eScopeType, scopeObj, pResult, cbResult );
2017 case k_ESteamNetworkingConfig_Float: return GetConfigValueTyped<float>( pEntry, eScopeType, scopeObj, pResult, cbResult );
2018 case k_ESteamNetworkingConfig_String: return GetConfigValueTyped<std::string>( pEntry, eScopeType, scopeObj, pResult, cbResult );
2019 case k_ESteamNetworkingConfig_Ptr: return GetConfigValueTyped<void *>( pEntry, eScopeType, scopeObj, pResult, cbResult );
2020 }
2021
2022 Assert( false ); // FIXME
2023 return k_ESteamNetworkingGetConfigValue_BadValue;
2024 }
2025
BEnumerateConfigValue(const GlobalConfigValueEntry * pVal)2026 static bool BEnumerateConfigValue( const GlobalConfigValueEntry *pVal )
2027 {
2028 if ( pVal->m_eDataType == k_ESteamNetworkingConfig_Ptr )
2029 return false;
2030
2031 switch ( pVal->m_eValue )
2032 {
2033 // Never enumerate these
2034 case k_ESteamNetworkingConfig_SymmetricConnect:
2035 case k_ESteamNetworkingConfig_LocalVirtualPort:
2036 case k_ESteamNetworkingConfig_ConnectionUserData:
2037 return false;
2038
2039 // Dev var?
2040 case k_ESteamNetworkingConfig_IP_AllowWithoutAuth:
2041 case k_ESteamNetworkingConfig_Unencrypted:
2042 case k_ESteamNetworkingConfig_EnumerateDevVars:
2043 case k_ESteamNetworkingConfig_SDRClient_FakeClusterPing:
2044 return g_Config_EnumerateDevVars.Get();
2045 }
2046
2047 return true;
2048 }
2049
GetConfigValueInfo(ESteamNetworkingConfigValue eValue,const char ** pOutName,ESteamNetworkingConfigDataType * pOutDataType,ESteamNetworkingConfigScope * pOutScope,ESteamNetworkingConfigValue * pOutNextValue)2050 bool CSteamNetworkingUtils::GetConfigValueInfo( ESteamNetworkingConfigValue eValue,
2051 const char **pOutName, ESteamNetworkingConfigDataType *pOutDataType,
2052 ESteamNetworkingConfigScope *pOutScope, ESteamNetworkingConfigValue *pOutNextValue )
2053 {
2054 const GlobalConfigValueEntry *pVal = FindConfigValueEntry( eValue );
2055 if ( pVal == nullptr )
2056 return false;
2057
2058 if ( pOutName )
2059 *pOutName = pVal->m_pszName;
2060 if ( pOutDataType )
2061 *pOutDataType = pVal->m_eDataType;
2062 if ( pOutScope )
2063 *pOutScope = pVal->m_eScope;
2064
2065 if ( pOutNextValue )
2066 {
2067 const GlobalConfigValueEntry *pNext = pVal;
2068 for (;;)
2069 {
2070 pNext = pNext->m_pNextEntry;
2071 if ( !pNext )
2072 {
2073 *pOutNextValue = k_ESteamNetworkingConfig_Invalid;
2074 break;
2075 }
2076 if ( BEnumerateConfigValue( pNext ) )
2077 {
2078 *pOutNextValue = pNext->m_eValue;
2079 break;
2080 }
2081 };
2082 }
2083
2084 return true;
2085 }
2086
GetFirstConfigValue()2087 ESteamNetworkingConfigValue CSteamNetworkingUtils::GetFirstConfigValue()
2088 {
2089 EnsureConfigValueTableInitted();
2090 Assert( BEnumerateConfigValue( s_vecConfigValueTable[0] ) );
2091 return s_vecConfigValueTable[0]->m_eValue;
2092 }
2093
2094
SteamNetworkingIPAddr_ToString(const SteamNetworkingIPAddr & addr,char * buf,size_t cbBuf,bool bWithPort)2095 void CSteamNetworkingUtils::SteamNetworkingIPAddr_ToString( const SteamNetworkingIPAddr &addr, char *buf, size_t cbBuf, bool bWithPort )
2096 {
2097 ::SteamNetworkingIPAddr_ToString( &addr, buf, cbBuf, bWithPort );
2098 }
2099
SteamNetworkingIPAddr_ParseString(SteamNetworkingIPAddr * pAddr,const char * pszStr)2100 bool CSteamNetworkingUtils::SteamNetworkingIPAddr_ParseString( SteamNetworkingIPAddr *pAddr, const char *pszStr )
2101 {
2102 return ::SteamNetworkingIPAddr_ParseString( pAddr, pszStr );
2103 }
2104
SteamNetworkingIdentity_ToString(const SteamNetworkingIdentity & identity,char * buf,size_t cbBuf)2105 void CSteamNetworkingUtils::SteamNetworkingIdentity_ToString( const SteamNetworkingIdentity &identity, char *buf, size_t cbBuf )
2106 {
2107 return ::SteamNetworkingIdentity_ToString( &identity, buf, cbBuf );
2108 }
2109
SteamNetworkingIdentity_ParseString(SteamNetworkingIdentity * pIdentity,const char * pszStr)2110 bool CSteamNetworkingUtils::SteamNetworkingIdentity_ParseString( SteamNetworkingIdentity *pIdentity, const char *pszStr )
2111 {
2112 return ::SteamNetworkingIdentity_ParseString( pIdentity, sizeof(SteamNetworkingIdentity), pszStr );
2113 }
2114
GetAppID()2115 AppId_t CSteamNetworkingUtils::GetAppID()
2116 {
2117 return m_nAppID;
2118 }
2119
TEST_ResetSelf()2120 void CSteamNetworkingUtils::TEST_ResetSelf()
2121 {
2122 m_nAppID = 0;
2123 }
2124
GetTimeSecure()2125 time_t CSteamNetworkingUtils::GetTimeSecure()
2126 {
2127 // Trusting local user's clock!
2128 return time(nullptr);
2129 }
2130
GetBuildString()2131 const char *CSteamNetworkingUtils::GetBuildString()
2132 {
2133 #if defined( STEAMNETWORKINGSOCKETS_OPENSOURCE )
2134 return "opensource " __DATE__ " " __TIME__;
2135 #elif defined( STEAMNETWORKINGSOCKETS_PARTNER )
2136 return "partner " __DATE__ " " __TIME__;
2137 #elif defined( STEAMNETWORKINGSOCKETS_STANDALONELIB )
2138 return "lib " __DATE__ " " __TIME__;
2139 #elif defined( STEAMNETWORKINGSOCKETS_STEAMCLIENT )
2140 return "steam "
2141 #ifdef BRANCH_MAIN
2142 "(main) "
2143 #elif !defined( BRANCH_REL_CLIENT )
2144 "(branch?) "
2145 #endif
2146 __DATE__ " " __TIME__;
2147 #elif defined( STEAMNETWORKINGSOCKETS_STREAMINGCLIENT )
2148 return "stream "
2149 #ifdef BRANCH_MAIN
2150 "(main) "
2151 #elif !defined( BRANCH_REL_CLIENT )
2152 "(branch?) "
2153 #endif
2154 __DATE__ " " __TIME__;
2155 #else
2156 #error "Huh?"
2157 #endif
2158 }
2159
GetPlatformString()2160 const char *CSteamNetworkingUtils::GetPlatformString()
2161 {
2162 #if defined( NN_NINTENDO_SDK )
2163 return "nswitch";
2164 #elif defined( _GAMECORE )
2165 // Is this right? This might actually require a system call.
2166 return "xboxx";
2167 #elif defined( _STADIA )
2168 // Not sure if this works.
2169 return "stadia";
2170 #elif defined( _XBOX_ONE )
2171 return "xbone";
2172 #elif defined( _PS4 )
2173 return "ps4";
2174 #elif defined( _PS5 )
2175 return "ps5";
2176 #elif defined( TVOS ) || defined( __TVOS__ )
2177 return "tvos";
2178 #elif defined( __APPLE__ )
2179 #if TARGET_OS_TV
2180 return "tvos";
2181 #elif TARGET_OS_IPHONE
2182 return "ios";
2183 #else
2184 return "osx";
2185 #endif
2186 #elif defined( OSX )
2187 return "osx";
2188 #elif defined( ANDROID ) || defined( __ANDROID__ )
2189 return "android";
2190 #elif defined( _WINDOWS )
2191 return "windows";
2192 #elif defined( LINUX ) || defined( __LINUX__ ) || defined(linux) || defined(__linux) || defined(__linux__)
2193 return "linux";
2194 #elif defined( FREEBSD ) || defined( __FreeBSD__ )
2195 return "freebsd";
2196 #else
2197 #error "Unknown platform"
2198 #endif
2199 }
2200
2201 } // namespace SteamNetworkingSocketsLib
2202 using namespace SteamNetworkingSocketsLib;
2203
2204 /////////////////////////////////////////////////////////////////////////////
2205 //
2206 // Global API interface
2207 //
2208 /////////////////////////////////////////////////////////////////////////////
2209
2210 #ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE
2211
2212 static CSteamNetworkingSockets *s_pSteamNetworkingSockets = nullptr;
2213
GameNetworkingSockets_Init(const SteamNetworkingIdentity * pIdentity,SteamNetworkingErrMsg & errMsg)2214 STEAMNETWORKINGSOCKETS_INTERFACE bool GameNetworkingSockets_Init( const SteamNetworkingIdentity *pIdentity, SteamNetworkingErrMsg &errMsg )
2215 {
2216 SteamNetworkingGlobalLock lock( "GameNetworkingSockets_Init" );
2217
2218 // Already initted?
2219 if ( s_pSteamNetworkingSockets )
2220 {
2221 AssertMsg( false, "GameNetworkingSockets_init called multiple times?" );
2222 return true;
2223 }
2224
2225 // Init basic functionality
2226 CSteamNetworkingSockets *pSteamNetworkingSockets = new CSteamNetworkingSockets( ( CSteamNetworkingUtils *)SteamNetworkingUtils() );
2227 if ( !pSteamNetworkingSockets->BInitGameNetworkingSockets( pIdentity, errMsg ) )
2228 {
2229 pSteamNetworkingSockets->Destroy();
2230 return false;
2231 }
2232
2233 s_pSteamNetworkingSockets = pSteamNetworkingSockets;
2234 return true;
2235 }
2236
GameNetworkingSockets_Kill()2237 STEAMNETWORKINGSOCKETS_INTERFACE void GameNetworkingSockets_Kill()
2238 {
2239 SteamNetworkingGlobalLock lock( "GameNetworkingSockets_Kill" );
2240 if ( s_pSteamNetworkingSockets )
2241 {
2242 s_pSteamNetworkingSockets->Destroy();
2243 s_pSteamNetworkingSockets = nullptr;
2244 }
2245 }
2246
SteamNetworkingSockets_LibV9()2247 STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamNetworkingSockets_LibV9()
2248 {
2249 return s_pSteamNetworkingSockets;
2250 }
2251
SteamNetworkingUtils_LibV3()2252 STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingUtils *SteamNetworkingUtils_LibV3()
2253 {
2254 static CSteamNetworkingUtils s_utils;
2255 return &s_utils;
2256 }
2257
2258 #endif
2259