From ef23889bc59d2ebbab8ff690905293196c5e342d Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Sat, 29 Aug 2020 16:46:07 -0700 Subject: [PATCH 01/14] Move custom signaling interfaces to a separate file. These are advanced interfaces and we don't wnat them cluttering up the main file. Also sync up a few cosmetic differences with Steam branch. --- include/steam/isteamnetworkingsockets.h | 86 ++--------------- .../steam/steamnetworkingcustomsignaling.h | 92 +++++++++++++++++++ .../clientlib/steamnetworkingsockets_p2p.h | 1 + 3 files changed, 100 insertions(+), 79 deletions(-) create mode 100644 include/steam/steamnetworkingcustomsignaling.h diff --git a/include/steam/isteamnetworkingsockets.h b/include/steam/isteamnetworkingsockets.h index f97e5b0..a66739c 100644 --- a/include/steam/isteamnetworkingsockets.h +++ b/include/steam/isteamnetworkingsockets.h @@ -557,9 +557,9 @@ public: /// NOTE: The routing blob returned here is not encrypted. Send it to your backend /// and don't share it directly with clients. virtual EResult GetGameCoordinatorServerLogin( SteamDatagramGameCoordinatorServerLogin *pLoginInfo, int *pcbSignedBlob, void *pBlob ) = 0; - #endif // #ifndef STEAMNETWORKINGSOCKETS_ENABLE_SDR + // // Relayed connections using custom signaling protocol // @@ -661,87 +661,15 @@ protected: }; #define STEAMNETWORKINGSOCKETS_INTERFACE_VERSION "SteamNetworkingSockets009" -/// Interface used to send signaling messages for a particular connection. -/// You will need to construct one of these per connection. -/// -/// - For connections initiated locally, you will construct it and pass -/// it to ISteamNetworkingSockets::ConnectP2PCustomSignaling. -/// - For connections initiated remotely and "accepted" locally, you -/// will return it from ISteamNetworkingCustomSignalingRecvContext::OnConnectRequest -class ISteamNetworkingConnectionCustomSignaling -{ -public: - /// Called to send a rendezvous message to the remote peer. This may be called - /// from any thread, at any time, so you need to be thread-safe! Don't take - /// any locks that might hold while calling into SteamNetworkingSockets functions, - /// because this could lead to deadlocks. - /// - /// Note that when initiating a connection, we may not know the identity - /// of the peer, if you did not specify it in ConnectP2PCustomSignaling. - /// - /// Return true if a best-effort attempt was made to deliver the message. - /// If you return false, it is assumed that the situation is fatal; - /// the connection will be closed, and Release() will be called - /// eventually. - /// - /// Signaling objects will not be shared between connections. - /// You can assume that the same value of hConn will be used - /// every time. - virtual bool SendSignal( HSteamNetConnection hConn, const SteamNetConnectionInfo_t &info, const void *pMsg, int cbMsg ) = 0; - - /// Called when the connection no longer needs to send signals. - /// Note that this happens eventually (but not immediately) after - /// the connection is closed. Signals may need to be sent for a brief - /// time after the connection is closed, to clean up the connection. - virtual void Release() = 0; -}; - -/// Interface used when a custom signal is received. -/// See ISteamNetworkingSockets::ReceivedP2PCustomSignal -class ISteamNetworkingCustomSignalingRecvContext -{ -public: - - /// Called when the signal represents a request for a new connection. - /// - /// If you want to ignore the request, just return NULL. In this case, - /// the peer will NOT receive any reply. You should consider ignoring - /// requests rather than actively rejecting them, as a security measure. - /// If you actively reject requests, then this makes it possible to detect - /// if a user is online or not, just by sending them a request. - /// - /// If you wish to send back a rejection, then use - /// ISteamNetworkingSockets::CloseConnection() and then return NULL. - /// We will marshal a properly formatted rejection signal and - /// call SendRejectionSignal() so you can send it to them. - /// - /// If you return a signaling object, the connection is NOT immediately - /// accepted by default. Instead, it stays in the "connecting" state, - /// and the usual callback is posted, and your app can accept the - /// connection using ISteamNetworkingSockets::AcceptConnection. This - /// may be useful so that these sorts of connections can be more similar - /// to your application code as other types of connections accepted on - /// a listen socket. If this is not useful and you want to skip this - /// callback process and immediately accept the connection, call - /// ISteamNetworkingSockets::AcceptConnection before returning the - /// signaling object. - /// - /// After accepting a connection (through either means), the connection - /// will transition into the "finding route" state. - virtual ISteamNetworkingConnectionCustomSignaling *OnConnectRequest( HSteamNetConnection hConn, const SteamNetworkingIdentity &identityPeer, int nLocalVirtualPort ) = 0; - - /// This is called actively communication rejection or failure - /// to the incoming message. If you intend to ignore all incoming requests - /// that you do not wish to accept, then it's not strictly necessary to - /// implement this. - virtual void SendRejectionSignal( const SteamNetworkingIdentity &identityPeer, const void *pMsg, int cbMsg ) = 0; -}; // Global accessor. #if defined( STEAMNETWORKINGSOCKETS_PARTNER ) - // Standalone lib - STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamNetworkingSockets(); - STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamGameServerNetworkingSockets(); + // Standalone lib. Use different symbol name, so that we can dynamically switch between steamclient.dll + // and the standalone lib + STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamNetworkingSockets_Lib(); + STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamGameServerNetworkingSockets_Lib(); + inline ISteamNetworkingSockets *SteamNetworkingSockets() { return SteamNetworkingSockets_Lib(); } + inline ISteamNetworkingSockets *SteamGameServerNetworkingSockets() { return SteamGameServerNetworkingSockets_Lib(); } #elif defined( STEAMNETWORKINGSOCKETS_OPENSOURCE ) || defined( STEAMNETWORKINGSOCKETS_STREAMINGCLIENT ) diff --git a/include/steam/steamnetworkingcustomsignaling.h b/include/steam/steamnetworkingcustomsignaling.h new file mode 100644 index 0000000..e95be93 --- /dev/null +++ b/include/steam/steamnetworkingcustomsignaling.h @@ -0,0 +1,92 @@ +//====== Copyright Valve Corporation, All rights reserved. ==================== +// +// Interfaces needed to implement your own P2P signaling service. If you +// aren't using P2P connections, or you can use the default service provided +// by the platform (e.g. a typical Steam game), then you don't need anything +// in this file. +// +//============================================================================= + +#ifndef STEAMNETWORKINGCUSTOMSIGNALING +#define STEAMNETWORKINGCUSTOMSIGNALING +#pragma once + +#include "steamnetworkingtypes.h" + +/// Interface used to send signaling messages for a particular connection. +/// +/// - For connections initiated locally, you will construct it and pass +/// it to ISteamNetworkingSockets::ConnectP2PCustomSignaling. +/// - For connections initiated remotely and "accepted" locally, you +/// will return it from ISteamNetworkingCustomSignalingRecvContext::OnConnectRequest +class ISteamNetworkingConnectionCustomSignaling +{ +public: + /// Called to send a rendezvous message to the remote peer. This may be called + /// from any thread, at any time, so you need to be thread-safe! Don't take + /// any locks that might hold while calling into SteamNetworkingSockets functions, + /// because this could lead to deadlocks. + /// + /// Note that when initiating a connection, we may not know the identity + /// of the peer, if you did not specify it in ConnectP2PCustomSignaling. + /// + /// Return true if a best-effort attempt was made to deliver the message. + /// If you return false, it is assumed that the situation is fatal; + /// the connection will be closed, and Release() will be called + /// eventually. + /// + /// Signaling objects will not be shared between connections. + /// You can assume that the same value of hConn will be used + /// every time. + virtual bool SendSignal( HSteamNetConnection hConn, const SteamNetConnectionInfo_t &info, const void *pMsg, int cbMsg ) = 0; + + /// Called when the connection no longer needs to send signals. + /// Note that this happens eventually (but not immediately) after + /// the connection is closed. Signals may need to be sent for a brief + /// time after the connection is closed, to clean up the connection. + virtual void Release() = 0; +}; + +/// Interface used when a custom signal is received. +/// See ISteamNetworkingSockets::ReceivedP2PCustomSignal +class ISteamNetworkingCustomSignalingRecvContext +{ +public: + + /// Called when the signal represents a request for a new connection. + /// + /// If you want to ignore the request, just return NULL. In this case, + /// the peer will NOT receive any reply. You should consider ignoring + /// requests rather than actively rejecting them, as a security measure. + /// If you actively reject requests, then this makes it possible to detect + /// if a user is online or not, just by sending them a request. + /// + /// If you wish to send back a rejection, then use + /// ISteamNetworkingSockets::CloseConnection() and then return NULL. + /// We will marshal a properly formatted rejection signal and + /// call SendRejectionSignal() so you can send it to them. + /// + /// If you return a signaling object, the connection is NOT immediately + /// accepted by default. Instead, it stays in the "connecting" state, + /// and the usual callback is posted, and your app can accept the + /// connection using ISteamNetworkingSockets::AcceptConnection. This + /// may be useful so that these sorts of connections can be more similar + /// to your application code as other types of connections accepted on + /// a listen socket. If this is not useful and you want to skip this + /// callback process and immediately accept the connection, call + /// ISteamNetworkingSockets::AcceptConnection before returning the + /// signaling object. + /// + /// After accepting a connection (through either means), the connection + /// will transition into the "finding route" state. + virtual ISteamNetworkingConnectionCustomSignaling *OnConnectRequest( HSteamNetConnection hConn, const SteamNetworkingIdentity &identityPeer, int nLocalVirtualPort ) = 0; + + /// This is called to actively communicate rejection or failure + /// to the incoming message. If you intend to ignore all incoming requests + /// that you do not wish to accept, then it's not strictly necessary to + /// implement this. + virtual void SendRejectionSignal( const SteamNetworkingIdentity &identityPeer, const void *pMsg, int cbMsg ) = 0; +}; + +#endif // STEAMNETWORKINGCUSTOMSIGNALING + diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_p2p.h b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_p2p.h index efec08f..1749695 100644 --- a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_p2p.h +++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_p2p.h @@ -4,6 +4,7 @@ #define STEAMNETWORKINGSOCKETS_P2P_H #pragma once +#include #include "steamnetworkingsockets_connections.h" #include "csteamnetworkingsockets.h" From f3b9884170d7fabfa52d7ab2e55f55f246d38e43 Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Sat, 29 Aug 2020 21:01:23 -0700 Subject: [PATCH 02/14] Add a trivial P2P signaling server. It has a really dumb client protocol, no authentication, etc. But it should be sufficient to illustrate how a plugin for a real signaling service (maybe XMPP or DERP) could be written. --- examples/CMakeLists.txt | 27 +++++- examples/trivial_signaling_server.go | 123 +++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 examples/trivial_signaling_server.go diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index a798ec9..2b4f9f3 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,6 +1,31 @@ cmake_minimum_required(VERSION 3.5) project(gns_example C CXX) +find_library(golang REQUIRED) + +# +# Trivial signaling server, written in go +# +set(SIGNAL_SERVER_TARGET trivial_signaling_server) +set(SIGNAL_SERVER_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/trivial_signaling_server) +set(SIGNAL_SERVER_SRCS + trivial_signaling_server.go +) +add_custom_command( + OUTPUT ${SIGNAL_SERVER_OUTPUT} + DEPENDS ${SIGNAL_SERVER_SRCS} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Building GO Trivial signaling server" + COMMAND env go build -o "${SIGNAL_SERVER_OUTPUT}" ${CMAKE_GO_FLAGS} ${SIGNAL_SERVER_SRCS} +) +add_custom_target(${SIGNAL_SERVER_TARGET}_all ALL DEPENDS ${SIGNAL_SERVER_OUTPUT}) +#add_custom_target(${SIGNAL_SERVER_TARGET} ALL) +#add_custom_target(${SIGNAL_SERVER_OUTPUT} ALL) + +# +# Really simple client/server chat +# + add_executable( example_chat example_chat.cpp) @@ -15,5 +40,3 @@ target_link_libraries(example_chat GameNetworkingSockets::GameNetworkingSockets) if(COMMAND add_sanitizers) add_sanitizers(example_chat) endif() - -# vim: set ts=4 sts=4 sw=4 noet: diff --git a/examples/trivial_signaling_server.go b/examples/trivial_signaling_server.go new file mode 100644 index 0000000..626a992 --- /dev/null +++ b/examples/trivial_signaling_server.go @@ -0,0 +1,123 @@ +package main + +import ( + "strings" + "fmt" + "net" + "flag" + "bufio" + "log" +) + +// Current list of client connections +var g_mapClientConnections = make(map[string]net.Conn) + +// Goroutine to service a client connection +func ServiceConnection(conn net.Conn) { + + // Save off address + addr := conn.RemoteAddr().String() + + // Attach a Reader object to the connection, so we can read from it easily + in := bufio.NewReader(conn) + + // In our trivial protocol, the first line contains the client identity + // on a line by itself + intro,err := in.ReadString('\n') + if err != nil { + log.Printf( "[%s] Aborting connection before we ever received client identity", addr ) + conn.Close() + } + identity := strings.TrimSpace(intro) + + // Amnnnnnd that's it. No authentication. + + // Locate existing connection, if any. + existingConn := g_mapClientConnections[identity] + + // Add us to map or replace existing entry + g_mapClientConnections[identity] = conn + + // Now handle existing entry + if existingConn != nil { + log.Printf( "[%s@%s] Closing connection to make room for new connection from '%s'", identity, existingConn.RemoteAddr().String(), addr ) + existingConn.Close() + } + log.Printf( "[%s@%s] Added connection", identity, addr ) + + // Keep reading until connection is closed + for { + line,err := in.ReadString('\n') + if err != nil { + conn.Close() + + // Are we stil in the map? + if g_mapClientConnections[identity] == conn { + log.Printf( "[%s@%s] Connecton closed. %s", identity, addr, err ) + delete(g_mapClientConnections, identity) + } else { + // Assume it's because we got replaced by another connection. + // The other connection already logged, so don't do anything here + } + break; + } + + // Our protocol is just [destination peer identity] [payload] + // And everything is in text. + dest_and_msg := strings.SplitN(line, " ", 2) + if len(dest_and_msg) != 2 { + log.Printf("[%s@%s] Ignoring weird input '%s' (maybe truncated?)", identity, addr, line) + continue + } + dest_identity := strings.TrimSpace(dest_and_msg[0]); + payload := dest_and_msg[1] + + // Locate the destination peer's connection. + dest_conn := g_mapClientConnections[dest_identity] + if dest_conn == nil { + log.Printf("[%s@%s] Ignoring, destination peer '%s' not found", identity, addr, dest_identity) + continue + } + + // Format new message, putting the sender's identity in front. + msg := identity + " " + payload + + // Send to the peer + dest_conn.Write([]byte(msg)) + + // Lawg + log.Printf("[%s@%s] -> %s (%d chars)", identity, addr, dest_identity, len(payload)) + } + +} + +// Main entry point +func main() { + + // Parse command line flags + port := flag.Int("port", 10000, "Port to listen on") + flag.Parse() + listen_addr := fmt.Sprintf("0.0.0.0:%d", *port) + + // Start listening + listener, err := net.Listen("tcp", listen_addr) + if err != nil { + log.Panic(err) + } + log.Printf("Listening at %s", listen_addr) + + // Main loop + for { + + // Wait for the next incoming connection + conn,err := listener.Accept() + if err != nil { + log.Panic(err) + } + + // Start goroutine to service it + go ServiceConnection(conn) + } + +} + From 56adfc703768c413b6e1487e9c5714beb19dda82 Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Sat, 29 Aug 2020 21:33:44 -0700 Subject: [PATCH 03/14] Add a comment to desribe what this is. --- examples/trivial_signaling_server.go | 43 +++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/examples/trivial_signaling_server.go b/examples/trivial_signaling_server.go index 626a992..005f006 100644 --- a/examples/trivial_signaling_server.go +++ b/examples/trivial_signaling_server.go @@ -1,3 +1,42 @@ +// Really simple P2P signaling server. +// +// When establishing peer-to-peer connections, the peers +// need some sort of pre-arranged side channels that they +// can use to exchange messages. This channel is assumed +// to be relatively low bandwidth and high latency. This +// service is often called "signaling". +// +// This server has the following really simple protocol: +// It listens on a particular TCP port. Clients connect +// raw TCP. The protocol is text-based and line oriented, +// so it is easy to test using telnet. When a client +// connects, it should send its identity on the first line. +// Afterwards, clients can send a message to a peer by +// sending a line formatted as follows: +// +// DESTINATION_IDENTITY PAYLOAD +// +// Identites may not contain spaces, and the payload +// should be plain ASCII text. (Hex or base64 encode it). +// +// If there is a client with that destination identity, +// then the server will forward the message on. Otherwise +// it is discarded. +// +// Forwarded messages have basically the same format and +// are the only type of message the server ever sends to the +// client. The only difference is that the identity is the +// identity of the sender. +// +// This is just an example code to illustrate what a +// signaling service is. A real production server would +// probably need to be able to scale across multiple +// processes, and provide authentication and rate +// limiting. +// +// Note that SteamNetworkingSockets use of signaling +// service does NOT assume guaranteed delivery. + package main import ( @@ -9,6 +48,8 @@ import ( "log" ) +const DEFAULT_LISTEN_PORT = 10000 + // Current list of client connections var g_mapClientConnections = make(map[string]net.Conn) @@ -95,7 +136,7 @@ func ServiceConnection(conn net.Conn) { func main() { // Parse command line flags - port := flag.Int("port", 10000, "Port to listen on") + port := flag.Int("port", DEFAULT_LISTEN_PORT, "Port to listen on") flag.Parse() listen_addr := fmt.Sprintf("0.0.0.0:%d", *port) From c52dd13dbaeba395f7eaa905709bea6d087cd1c5 Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Sat, 29 Aug 2020 22:41:20 -0700 Subject: [PATCH 04/14] Add golang package to travis configurations. Needed to compile the example trivial P2P signaling server --- .travis/install-alpine.sh | 1 + .travis/install-archlinux.sh | 1 + .travis/install-fedora.sh | 1 + .travis/install-ubuntu.sh | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis/install-alpine.sh b/.travis/install-alpine.sh index f1672bb..b44708a 100755 --- a/.travis/install-alpine.sh +++ b/.travis/install-alpine.sh @@ -18,6 +18,7 @@ PACKAGES=( pkgconf git linux-headers + golang ) PACKAGES+=(protobuf-dev) diff --git a/.travis/install-archlinux.sh b/.travis/install-archlinux.sh index f0302d2..b25900e 100755 --- a/.travis/install-archlinux.sh +++ b/.travis/install-archlinux.sh @@ -12,6 +12,7 @@ PACKAGES=( cmake meson git + golang ) PACKAGES+=(protobuf) diff --git a/.travis/install-fedora.sh b/.travis/install-fedora.sh index aa3749c..fcf46bb 100755 --- a/.travis/install-fedora.sh +++ b/.travis/install-fedora.sh @@ -16,6 +16,7 @@ PACKAGES=( pkgconf-pkg-config git make + golang ) PACKAGES+=(protobuf-compiler protobuf-devel) diff --git a/.travis/install-ubuntu.sh b/.travis/install-ubuntu.sh index b5a6731..063d31c 100755 --- a/.travis/install-ubuntu.sh +++ b/.travis/install-ubuntu.sh @@ -10,7 +10,7 @@ apt-get update apt-get install -y locales locale-gen en_US.UTF-8 -PACKAGES=(build-essential pkg-config ccache cmake meson clang git) +PACKAGES=(build-essential pkg-config ccache cmake meson clang git golang) PACKAGES+=(libprotobuf-dev protobuf-compiler) PACKAGES+=(libssl-dev) From b60a73ff7d3633b9a22d94379671d8bcba948302 Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Sun, 30 Aug 2020 00:26:58 -0700 Subject: [PATCH 05/14] Add include --- .../clientlib/steamnetworkingsockets_p2p.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_p2p.cpp b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_p2p.cpp index d6c192a..8bd5ccd 100644 --- a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_p2p.cpp +++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_p2p.cpp @@ -4,6 +4,10 @@ #include "csteamnetworkingsockets.h" #include "crypto.h" +#ifdef POSIX + #include +#endif + #ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR #include "steamnetworkingsockets_sdr_p2p.h" #endif From cbefc8aebd0d8f88267714dcbf1e52d43c44abcd Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Sun, 30 Aug 2020 11:55:44 -0700 Subject: [PATCH 06/14] Add trivial signaling client example. WIP. This compiles but hasn't been tested yet. Working on a test case that exercises it. Also I'm still not sure I like the way the interfaces are organized. It's slightly more complicated than necessary, to handle what is probably a relatively rare use case. Maybe the only use case I care about is "Here is the one and only signaling service to use" and I could essentially combine ISteamNetworkingCustomSignalingRecvContext and ISteamNetworkingCustomSignalingService. You could replace the default platform service, but not mix and match. Or maybe the only thing that is needed is some comments clarifying the relationship between ISteamNetworkingCustomSignalingRecvContext and ISteamNetworkingCustomSignalingService. In both cases, the primary purpose is to create a signaling session for a specific connection. But one is for remotely initiated connections (a signal that respresents a connect request coming in) and the other is for locally-initated requests that do not go through ConnectP2PCustomSignaling. --- examples/trivial_signaling_client.cpp | 320 ++++++++++++++++++++++++++ examples/trivial_signaling_client.h | 57 +++++ 2 files changed, 377 insertions(+) create mode 100644 examples/trivial_signaling_client.cpp create mode 100644 examples/trivial_signaling_client.h diff --git a/examples/trivial_signaling_client.cpp b/examples/trivial_signaling_client.cpp new file mode 100644 index 0000000..7c6be63 --- /dev/null +++ b/examples/trivial_signaling_client.cpp @@ -0,0 +1,320 @@ +// Client of our dummy trivial signaling server service. +// Serves as an example of you how to hook up signaling server +// to SteamNetworkingSockets P2P connections + +#include +#include +#include + +#include "trivial_signaling_client.h" +#include +#include + +#ifdef POSIX + #include + #include + #include + typedef int SOCKET; + constexpr SOCKET INVALID_SOCKET = -1; + inline void closesocket( SOCKET s ) { close(s); } + inline int GetSocketError() { return errno; } +#endif +#ifdef _WIN32 +#endif + +inline int HexDigitVal( char c ) +{ + if ( '0' <= c && c <= '9' ) + return c - '0'; + if ( 'a' <= c && c <= 'f' ) + return c - 'a' + 0xa; + if ( 'A' <= c && c <= 'F' ) + return c - 'A' + 0xa; + return -1; +} + +/// Implementation of ITrivialSignalingClient +class CTrivialSignalingClient : public ITrivialSignalingClient +{ + + // This is the thing we'll actually create to send signals for a particular + // connection. + struct ConnectionSignaling : ISteamNetworkingConnectionCustomSignaling + { + CTrivialSignalingClient *const m_pOwner; + std::string const m_sPeerIdentity; // Save off the string encoding of the identity we're talking to + + ConnectionSignaling( CTrivialSignalingClient *owner, const char *pszPeerIdentity ) + : m_pOwner( owner ) + , m_sPeerIdentity( pszPeerIdentity ) + { + } + + // + // Implements ISteamNetworkingConnectionCustomSignaling + // + + // This is called from SteamNetworkingSockets to send a signal. This could be called from any thread, + // so we need to be threadsafe, and avoid duoing slow stuff or calling back into SteamNetworkingSockets + virtual bool SendSignal( HSteamNetConnection hConn, const SteamNetConnectionInfo_t &info, const void *pMsg, int cbMsg ) override + { + + // We'll use a dumb hex encoding. + std::string signal; + signal.reserve( m_sPeerIdentity.length() + cbMsg*2 + 4 ); + signal.append( m_sPeerIdentity ); + signal.push_back( ' ' ); + for ( const uint8_t *p = (const uint8_t *)pMsg ; cbMsg > 0 ; --cbMsg, ++p ) + { + static const char hexdigit[] = "0123456789abcdef"; + signal.push_back( hexdigit[ *p >> 4U ] ); + signal.push_back( hexdigit[ *p & 0xf ] ); + } + signal.push_back('\n'); + + m_pOwner->Send( signal ); + } + + // Self destruct. This will be called by SteamNetworkingSockets when it's done with us. + virtual void Release() override + { + delete this; + } + }; + + sockaddr_storage m_adrServer; + size_t const m_adrServerSize; + ISteamNetworkingSockets *const m_pSteamNetworkingSockets; + std::string m_sGreeting; + + std::mutex sockMutex; + SOCKET m_sock; + std::string m_sBufferedData; + + void CloseSocket() + { + if ( m_sock != INVALID_SOCKET ) + { + closesocket( m_sock ); + m_sock = INVALID_SOCKET; + } + m_sBufferedData.clear(); + } + + void Connect() + { + CloseSocket(); + + int sockType = SOCK_STREAM; + #ifdef LINUX + sockType |= SOCK_CLOEXEC; + #endif + #if !defined( _WIN32 ) + sockType |= SOCK_NONBLOCK; + #endif + m_sock = socket( m_adrServer.ss_family, sockType, IPPROTO_TCP ); + if ( m_sock == INVALID_SOCKET ) + return; + #ifdef _WIN32 + opt = 1; + if ( ioctlsocket( sock, FIONBIO, (unsigned long*)&opt ) == -1 ) + { + CloseSocket(); + return; + } + #endif + + connect( m_sock, (const sockaddr *)&m_adrServer, (socklen_t )m_adrServerSize ); + } + +public: + CTrivialSignalingClient( const sockaddr *adrServer, size_t adrServerSize, ISteamNetworkingSockets *pSteamNetworkingSockets ) + : m_adrServerSize( adrServerSize ), m_pSteamNetworkingSockets( pSteamNetworkingSockets ) + { + memcpy( &m_adrServer, adrServer, adrServerSize ); + m_sock = INVALID_SOCKET; + + // Save off our identity + SteamNetworkingIdentity identitySelf; identitySelf.Clear(); + pSteamNetworkingSockets->GetIdentity( &identitySelf ); + assert( !identitySelf.IsInvalid() ); + assert( !identitySelf.IsLocalHost() ); // We need something more specific than that + m_sGreeting = SteamNetworkingIdentityRender( identitySelf ).c_str(); + assert( strchr( m_sGreeting.c_str(), ' ' ) == nullptr ); // Our protocol is dumb and doesn't support this + m_sGreeting.push_back( '\n' ); + + // Begin connecting immediately + Connect(); + } + + // Send the signal. + void Send( const std::string &s ) + { + assert( s.length() > 0 && s[ s.length()-1 ] == '\n' ); // All of our signals are '\n'-terminated + + sockMutex.lock(); + if ( m_sock != INVALID_SOCKET ) + { + int l = s.length(); + int r = ::send( m_sock, s.c_str(), s.length(), 0 ); + if ( r != l && r != 0 ) + { + // Socket hosed, or we sent a partial signal. + // We need to restart connection + CloseSocket(); + } + } + sockMutex.unlock(); + } + + // + // Implements ISteamNetworkingCustomSignalingService + // + virtual ISteamNetworkingConnectionCustomSignaling *CreateSignalingForConnection( + const SteamNetworkingIdentity &identityPeer, + const char *pszRoutingInfo, + SteamNetworkingErrMsg &errMsg + ) override { + SteamNetworkingIdentityRender sIdentityPeer( identityPeer ); + + // FIXME - here we really ouight to confirm that the string version of the + // identity does not have spaces, since our protocol doesn't permit it. + + return new ConnectionSignaling( this, sIdentityPeer.c_str() ); + } + + virtual void Poll() override + { + // Drain the socket into the buffer, and check for reconnecting + sockMutex.lock(); + if ( m_sock != INVALID_SOCKET ) + { + Connect(); + } + else + { + for (;;) + { + char buf[256]; + int r = recv( m_sock, buf, sizeof(buf), 0 ); + if ( r == 0 ) + break; + if ( r < 0 ) + { + int e = GetSocketError(); + if ( e != EAGAIN && e != EWOULDBLOCK ) + CloseSocket(); + break; + } + + m_sBufferedData.append( buf, r ); + } + } + + // Release the lock now. See the notes below about why it's very important + // to release the lock early and not hold it while we try to dispatch the + // received callbacks. + sockMutex.unlock(); + + // Now dispatch any buffered signals + for (;;) + { + + // Find end of line. Do we have a complete signal? + size_t l = m_sBufferedData.find( '\n' ); + if ( l == std::string::npos ) + break; + + // Locate the space that seperates [from] [payload] + size_t spc = m_sBufferedData.find( ' ' ); + if ( spc != std::string::npos && spc < l ) + { + + // Hex decode the payload. As it turns out, we actually don't + // need the sender's identity. The payload has everything needed + // to process the message. Maybe we should remove it from our + // dummy signaling protocol? It might be useful for debugging, tho. + std::string data; data.reserve( ( l - spc ) / 2 ); + for ( size_t i = spc+1 ; i+2 <= l ; i += 2 ) + { + int h = HexDigitVal( m_sBufferedData[i] ); + int l = HexDigitVal( m_sBufferedData[i+1] ); + if ( ( h | l ) & ~0xf ) + { + assert(false); // Failed hex decode. Not a bug in our code here, but this is just example code, so we'll handle it this way + goto next_message; + } + data.push_back( (char)(h<<4 | l ) ); + } + + // Setup a context object that can respond if this signal is a connection request. + struct Context : ISteamNetworkingCustomSignalingRecvContext + { + CTrivialSignalingClient *m_pOwner; + + virtual ISteamNetworkingConnectionCustomSignaling *OnConnectRequest( + HSteamNetConnection hConn, + const SteamNetworkingIdentity &identityPeer, + int nLocalVirtualPort + ) override { + + // We will just always handle requests thorugh the usual listen socket state + // machine. See the docuemntation for this function for other behaviour we + // might take. + + // Also, note that if there was routing/session info, it should have been in + // our envelope that we know how to parse, and we should save it off in this + // context object. + SteamNetworkingErrMsg ignoreErrMsg; + return m_pOwner->CreateSignalingForConnection( identityPeer, nullptr, ignoreErrMsg ); + } + + virtual void SendRejectionSignal( + const SteamNetworkingIdentity &identityPeer, + const void *pMsg, int cbMsg + ) override { + + // We'll just silently ignore all failures. This is actually the more secure + // Way to handle it in many cases. Actively returning failure might allow + // an attacker to just scrape random peers to see who is online. If you know + // the peer has a good reason for trying to connect, sending an active failure + // can improve error handling and the UX, instead of relying on timeout. But + // just consider the security implications. + } + }; + Context context; + context.m_pOwner = this; + + // Dispatch. + // Remember: From inside this function, our context object might get callbacks. + // And we might get asked to send signals, either now, or really at any time + // from any thread! If possible, avoid calling this function while holding locks. + // To process this call, SteamnetworkingSockets will need take its own internal lock. + // That lock may be held by another thread that is asking you to send a signal! So + // be warned that deadlocks are a possibility here. + m_pSteamNetworkingSockets->ReceivedP2PCustomSignal( data.c_str(), data.length(), &context ); + } + +next_message: + m_sBufferedData.erase( 0, l ); + } + } + + virtual void Release() + { + // NOTE: Here we are assuming that the calling code has already cleaned + // up all the connections, to keep the example simple. + CloseSocket(); + } +}; + +// Start connecting to the signaling server. +//ITrivialSignalingClient *CreateTrivialSignalingClient( +// const sockaddr *adrServer, size_t adrSize, // Address of the server. +// ISteamNetworkingSockets *pSteamNetworkingSockets, // Where should we send signals when we get them? +// SteamnetworkingErrMsg &errMsg // Error message is retjrned here if we fail +//); + + + + diff --git a/examples/trivial_signaling_client.h b/examples/trivial_signaling_client.h new file mode 100644 index 0000000..25297f8 --- /dev/null +++ b/examples/trivial_signaling_client.h @@ -0,0 +1,57 @@ +// Client of our dummy trivial signaling server service. +// Serves as an example of you how to hook up signaling server +// to SteamNetworkingSockets P2P connections + +#pragma once + +#include +struct sockaddr; +class ISteamNetworkingSockets; + +// FIXME - Eventually I intend to add a mechanism to set the default +// signaling service, so that SteamnetworkingSockets can +// initiate creation of signaling sessions. This will be the +// interface used for that. +/// Interface used to create signaling sessions to particular peers. +/// Typically this represents a connection to some service. +class ISteamNetworkingCustomSignalingService +{ +public: + + /// Setup a session for sending signals for a particular connection. + /// The signals will always be sent to the same peer. + /// + /// pszSessionRoutingInfo is reserved for future use, it will always + /// be NULL right now. + /// + /// On failure, return NULL + virtual ISteamNetworkingConnectionCustomSignaling *CreateSignalingForConnection( + const SteamNetworkingIdentity &identityPeer, + const char *pszRoutingInfo, + SteamNetworkingErrMsg &errMsg + ) = 0; +}; + +/// Interface to our client. +class ITrivialSignalingClient : public ISteamNetworkingCustomSignalingService +{ + + /// Poll the server for incoming signals and dispatch them. + /// We use polling in this example just to keep it simple. + /// You could use a service thread. + virtual void Poll() = 0; + + /// Disconnect from the server and close down our polling thread. + virtual void Release() = 0; +}; + +// Start connecting to the signaling server. +ITrivialSignalingClient *CreateTrivialSignalingClient( + const sockaddr *adrServer, size_t adrSize, // Address of the server. + ISteamNetworkingSockets *pSteamNetworkingSockets, // Where should we send signals when we get them? + SteamNetworkingErrMsg &errMsg // Error message is retjrned here if we fail +); + + + + From 47fcd6f919d7b20bca335b6db7d272c6f48d04c8 Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Sun, 30 Aug 2020 12:28:01 -0700 Subject: [PATCH 07/14] Fix name of go package on alpine linux. --- .travis/install-alpine.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/install-alpine.sh b/.travis/install-alpine.sh index b44708a..1cd9c48 100755 --- a/.travis/install-alpine.sh +++ b/.travis/install-alpine.sh @@ -18,7 +18,7 @@ PACKAGES=( pkgconf git linux-headers - golang + go ) PACKAGES+=(protobuf-dev) From f885094eedb0e2834846e487ed454c326e4d72b3 Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Sun, 30 Aug 2020 18:13:06 -0700 Subject: [PATCH 08/14] A few tweaks --- BUILDING.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/BUILDING.md b/BUILDING.md index baae286..9720145 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -13,8 +13,12 @@ Building * OpenSSL 1.1.x, plus ed25519-donna and curve25519-donna. (We've made some minor changes, so the source is included in this project.) * libsodium - * [bcrypt](https://docs.microsoft.com/en-us/windows/desktop/api/bcrypt/) (windows only) + * [bcrypt](https://docs.microsoft.com/en-us/windows/desktop/api/bcrypt/) + (windows only) * Google protobuf 2.6.1+ +* Google [webrtc](https://opensource.google/projects/webrtc) is used for + NAT piercing (ICE) for P2P connections. The relevant code is linked in as a + git submodule. You'll need to initialize that submodule to compile. ## Linux @@ -38,14 +42,7 @@ Arch Linux: ### Building -Using Meson: - -``` -$ meson . build -$ ninja -C build -``` - -Or CMake: +Using CMake (preferred): ``` $ mkdir build @@ -54,10 +51,16 @@ $ cmake -G Ninja .. $ ninja ``` +Or Meson (supported...kind-of): + +``` +$ meson . build +$ ninja -C build +``` + ## Windows / Visual Studio -On Windows, you can use the [vcpkg](https://github.com/microsoft/vcpkg/) package manager. Or setup the -dependencies by hand, which is a bit of an arduous gauntlet. +On Windows, you can use the [vcpkg](https://github.com/microsoft/vcpkg/) package manager. ### vcpkg @@ -77,6 +80,8 @@ To use libsodium as the crypto backend rather than OpenSSL, install `gamenetwork ### Manual setup +Setting up the dependencies by hand is a bit of an arduous gauntlet. + #### OpenSSL You can install the [OpenSSL binaries](https://slproweb.com/products/Win32OpenSSL.html) From b9b4165100d5a7a21ec96543d839da18f6ecd565 Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Mon, 31 Aug 2020 09:51:46 -0700 Subject: [PATCH 09/14] Add link to Facepunch.Steamworks --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eda44c1..ec50a57 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,11 @@ to facilitate binding to other languages. Third party language bindings: -* C#: -* Go: +* C#: + * + * +* Go: + * ## Roadmap From 1205aa893b63832ec7ef31a23a270f185e2a1ff0 Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Mon, 31 Aug 2020 10:55:26 -0700 Subject: [PATCH 10/14] Start P2P test. WIP. Moved some common stuff out of test_connection.cpp to test_common.cpp. --- tests/CMakeLists.txt | 8 ++ tests/test_common.cpp | 108 ++++++++++++++++++++++++ tests/test_common.h | 10 +++ tests/test_connection.cpp | 168 +++++++++----------------------------- tests/test_p2p.cpp | 29 +++++++ 5 files changed, 194 insertions(+), 129 deletions(-) create mode 100644 tests/test_common.cpp create mode 100644 tests/test_common.h create mode 100644 tests/test_p2p.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0953f00..ce022c8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,11 +7,19 @@ endif() add_executable( test_connection + test_common.cpp test_connection.cpp) +add_executable( + test_p2p + test_common.cpp + test_p2p.cpp + ../examples/trivial_signaling_client.cpp) if(SANITIZE) target_link_libraries(test_connection GameNetworkingSockets_s) + target_link_libraries(test_p2p GameNetworkingSockets_s) else() target_link_libraries(test_connection GameNetworkingSockets) + target_link_libraries(test_p2p GameNetworkingSockets) endif() add_sanitizers(test_connection) diff --git a/tests/test_common.cpp b/tests/test_common.cpp new file mode 100644 index 0000000..7bc263f --- /dev/null +++ b/tests/test_common.cpp @@ -0,0 +1,108 @@ +// Misc stuff used in the tests + +#include "test_common.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE +#include +#endif + +static FILE *g_fpLog = nullptr; +static SteamNetworkingMicroseconds g_logTimeZero; + +static void DebugOutput( ESteamNetworkingSocketsDebugOutputType eType, const char *pszMsg ) +{ + SteamNetworkingMicroseconds time = SteamNetworkingUtils()->GetLocalTimestamp() - g_logTimeZero; + if ( g_fpLog ) + fprintf( g_fpLog, "%10.6f %s\n", time*1e-6, pszMsg ); + if ( eType <= k_ESteamNetworkingSocketsDebugOutputType_Msg ) + { + printf( "%10.6f %s\n", time*1e-6, pszMsg ); + fflush(stdout); + } + if ( eType == k_ESteamNetworkingSocketsDebugOutputType_Bug ) + { + fflush(stdout); + fflush(stderr); + if ( g_fpLog ) + fflush( g_fpLog ); + + // !KLUDGE! Our logging (which is done while we hold the lock) + // is occasionally triggering this assert. Just ignroe that one + // error for now. + // Yes, this is a kludge. + if ( strstr( pszMsg, "SteamDatagramTransportLock held for" ) ) + return; + + assert( !"TEST FAILED" ); + } +} + +void TEST_Printf( const char *fmt, ... ) +{ + char text[ 2048 ]; + va_list ap; + va_start( ap, fmt ); + vsprintf( text, fmt, ap ); + va_end(ap); + char *nl = strchr( text, '\0' ) - 1; + if ( nl >= text && *nl == '\n' ) + *nl = '\0'; + DebugOutput( k_ESteamNetworkingSocketsDebugOutputType_Msg, text ); +} + +void TEST_Init( const SteamNetworkingIdentity *pIdentity ) +{ + g_fpLog = fopen( "log.txt", "wt" ); + g_logTimeZero = SteamNetworkingUtils()->GetLocalTimestamp(); + + SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Debug, DebugOutput ); + //SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Verbose, DebugOutput ); + //SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Msg, DebugOutput ); + + #ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE + SteamDatagramErrMsg errMsg; + if ( !GameNetworkingSockets_Init( pIdentity, errMsg ) ) + { + fprintf( stderr, "GameNetworkingSockets_Init failed. %s", errMsg ); + exit(1); + } + #else + //SteamAPI_Init(); + + SteamDatagramClient_SetAppID( 570 ); // Just set something, doesn't matter what + //SteamDatagramClient_SetUniverse( k_EUniverseDev ); + + SteamDatagramErrMsg errMsg; + if ( !SteamDatagramClient_Init( true, errMsg ) ) + { + fprintf( stderr, "SteamDatagramClient_Init failed. %s", errMsg ); + exit(1); + } + #endif +} + +void TEST_Kill() +{ + #ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE + GameNetworkingSockets_Kill(); + #else + SteamDatagramClient_Kill(); + #endif +} + +void TEST_PumpCallbacks() +{ + SteamNetworkingSockets()->RunCallbacks(); + std::this_thread::sleep_for( std::chrono::milliseconds( 2 ) ); +} + diff --git a/tests/test_common.h b/tests/test_common.h new file mode 100644 index 0000000..78612f6 --- /dev/null +++ b/tests/test_common.h @@ -0,0 +1,10 @@ +// Misc stuff used in the tests +#pragma once + +struct SteamNetworkingIdentity; + +extern void TEST_Init(); +extern void TEST_Printf( const char *fmt, ... ); +extern void TEST_Init( const SteamNetworkingIdentity *pIdentity ); +extern void TEST_Kill(); +extern void TEST_PumpCallbacks(); diff --git a/tests/test_connection.cpp b/tests/test_connection.cpp index 5c06038..42f287b 100644 --- a/tests/test_connection.cpp +++ b/tests/test_connection.cpp @@ -8,6 +8,8 @@ #include #include +#include "test_common.h" + #include #include #ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE @@ -19,93 +21,6 @@ static std::default_random_engine g_rand; static SteamNetworkingMicroseconds g_usecTestElapsed; -FILE *g_fpLog = nullptr; -SteamNetworkingMicroseconds g_logTimeZero; - -void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_t *pInfo ); - -static void DebugOutput( ESteamNetworkingSocketsDebugOutputType eType, const char *pszMsg ) -{ - SteamNetworkingMicroseconds time = SteamNetworkingUtils()->GetLocalTimestamp() - g_logTimeZero; - if ( g_fpLog ) - fprintf( g_fpLog, "%10.6f %s\n", time*1e-6, pszMsg ); - if ( eType <= k_ESteamNetworkingSocketsDebugOutputType_Msg ) - { - printf( "%10.6f %s\n", time*1e-6, pszMsg ); - fflush(stdout); - } - if ( eType == k_ESteamNetworkingSocketsDebugOutputType_Bug ) - { - fflush(stdout); - fflush(stderr); - if ( g_fpLog ) - fflush( g_fpLog ); - - // !KLUDGE! Our logging (which is done while we hold the lock) - // is occasionally triggering this assert. Just ignroe that one - // error for now. - // Yes, this is a kludge. - if ( strstr( pszMsg, "SteamDatagramTransportLock held for" ) ) - return; - - assert( !"TEST FAILED" ); - } -} - -static void Printf( const char *fmt, ... ) -{ - char text[ 2048 ]; - va_list ap; - va_start( ap, fmt ); - vsprintf( text, fmt, ap ); - va_end(ap); - char *nl = strchr( text, '\0' ) - 1; - if ( nl >= text && *nl == '\n' ) - *nl = '\0'; - DebugOutput( k_ESteamNetworkingSocketsDebugOutputType_Msg, text ); -} - -static void InitSteamDatagramConnectionSockets() -{ - g_fpLog = fopen( "log.txt", "wt" ); - g_logTimeZero = SteamNetworkingUtils()->GetLocalTimestamp(); - - SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Debug, DebugOutput ); - //SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Verbose, DebugOutput ); - //SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Msg, DebugOutput ); - SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged( OnSteamNetConnectionStatusChanged ); - - #ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE - SteamDatagramErrMsg errMsg; - if ( !GameNetworkingSockets_Init( nullptr, errMsg ) ) - { - fprintf( stderr, "GameNetworkingSockets_Init failed. %s", errMsg ); - exit(1); - } - #else - //SteamAPI_Init(); - - SteamDatagramClient_SetAppID( 570 ); // Just set something, doesn't matter what - //SteamDatagramClient_SetUniverse( k_EUniverseDev ); - - SteamDatagramErrMsg errMsg; - if ( !SteamDatagramClient_Init( true, errMsg ) ) - { - fprintf( stderr, "SteamDatagramClient_Init failed. %s", errMsg ); - exit(1); - } - #endif -} - -static void ShutdownSteamDatagramConnectionSockets() -{ - #ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE - GameNetworkingSockets_Kill(); - #else - SteamDatagramClient_Kill(); - #endif -} - static HSteamListenSocket g_hSteamListenSocket = k_HSteamListenSocket_Invalid; struct TestMsg @@ -186,7 +101,7 @@ struct SFakePeer if ( result != k_EResultOK ) { - Printf( "***ERROR ON Send: %s %.3f %s message %lld, %d bytes (pending %d bytes)\n", + TEST_Printf( "***ERROR ON Send: %s %.3f %s message %lld, %d bytes (pending %d bytes)\n", m_sName.c_str(), g_usecTestElapsed*1e-6, msg.m_bReliable ? "reliable" : "unreliable", @@ -197,7 +112,7 @@ struct SFakePeer } #if 0 else - Printf( "Send: %s %.3f %s message %lld, %d bytes (pending %d bytes)\n", + TEST_Printf( "Send: %s %.3f %s message %lld, %d bytes (pending %d bytes)\n", connection.m_sName.c_str(), g_usecTestElapsed*1e-6, msg.m_bReliable ? "reliable" : "unreliable", @@ -244,7 +159,7 @@ static void Recv( ISteamNetworkingSockets *pSteamSocketNetworking ) { // Print that it happened. - Printf( + TEST_Printf( "Recv: %s, %s MISMATCH NUM wanted %lld got %lld\n", pConnection->m_sName.c_str(), pTestMsg->m_bReliable ? "RELIABLE" : "UNRELIABLE", @@ -278,7 +193,7 @@ void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_ { case k_ESteamNetworkingConnectionState_ClosedByPeer: case k_ESteamNetworkingConnectionState_ProblemDetectedLocally: - Printf( "Steam Net connection %x %s, reason %d: %s\n", + TEST_Printf( "Steam Net connection %x %s, reason %d: %s\n", pInfo->m_hConn, ( pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ClosedByPeer ? "closed by peer" : "problem detected locally" ), pInfo->m_info.m_eEndReason, @@ -301,7 +216,7 @@ void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_ /* case k_ESteamNetworkingConnectionState_None: - Printf( "No steam Net connection %x (%s)\n", pInfo->m_hConn, pInfo->m_info.m_steamIDRemote.Render() ); + TEST_Printf( "No steam Net connection %x (%s)\n", pInfo->m_hConn, pInfo->m_info.m_steamIDRemote.Render() ); if ( g_hSteamNetConnection == pInfo->m_hConn ) { @@ -317,7 +232,7 @@ void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_ if ( g_hSteamListenSocket != k_HSteamListenSocket_Invalid && pInfo->m_info.m_hListenSocket == g_hSteamListenSocket ) { // Somebody's knocking - Printf( "[%s] Accepting\n", pInfo->m_info.m_szConnectionDescription ); + TEST_Printf( "[%s] Accepting\n", pInfo->m_info.m_szConnectionDescription ); g_peerServer.m_hSteamNetConnection = pInfo->m_hConn; g_peerServer.m_bIsConnected = true; SteamNetworkingSockets()->AcceptConnection( pInfo->m_hConn ); @@ -331,7 +246,7 @@ void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_ { g_peerClient.m_bIsConnected = true; } - Printf( "[%s] connected\n", pInfo->m_info.m_szConnectionDescription ); + TEST_Printf( "[%s] connected\n", pInfo->m_info.m_szConnectionDescription ); break; @@ -341,15 +256,9 @@ void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_ } } -static void PumpCallbacks() -{ - SteamNetworkingSockets()->RunCallbacks(); - std::this_thread::sleep_for( std::chrono::milliseconds( 2 ) ); -} - static void PumpCallbacksAndMakeSureStillConnected() { - PumpCallbacks(); + TEST_PumpCallbacks(); assert( g_peerClient.m_bIsConnected ); assert( g_peerServer.m_bIsConnected ); assert( g_peerServer.m_hSteamNetConnection != k_HSteamNetConnection_Invalid ); @@ -368,35 +277,35 @@ static void PrintStatus( const SFakePeer &p1, const SFakePeer &p2 ) { const SteamNetworkingQuickConnectionStatus &info1 = p1.m_info; const SteamNetworkingQuickConnectionStatus &info2 = p2.m_info; - Printf( "\n" ); - Printf( "%12s %12s\n", p1.m_sName.c_str(), p2.m_sName.c_str() ); - Printf( "%10dms %10dms Ping\n", info1.m_nPing, info2.m_nPing ); - Printf( "%12s %12s Quality\n", FormatQuality( info1.m_flConnectionQualityLocal ).c_str(), FormatQuality( info2.m_flConnectionQualityLocal ).c_str() ); - Printf( "%11.1fK %11.1fK Send buffer\n", ( info1.m_cbPendingReliable+info1.m_cbPendingUnreliable )/1024.0f, ( info2.m_cbPendingReliable+info2.m_cbPendingUnreliable )/1024.0f ); - Printf( "%11.1fK %11.1fK Send rate (app)\n", p1.m_flSendRate/1024.0f, p2.m_flSendRate/1024.0f ); - Printf( "%11.1fK %11.1fK Send rate (wire)\n", info1.m_flOutBytesPerSec/1024.0f, info2.m_flOutBytesPerSec/1024.0f ); - Printf( "%12.1f %12.1f Send pkts/sec (wire)\n", info1.m_flOutPacketsPerSec, info2.m_flOutPacketsPerSec ); - Printf( "%11.1fK %11.1fK Send bandwidth (estimate)\n", info1.m_nSendRateBytesPerSecond/1024.0f, info2.m_nSendRateBytesPerSecond/1024.0f ); - Printf( "%11.1fK %11.1fK Recv rate (app)\n", p1.m_flRecvRate/1024.0f, p2.m_flRecvRate/1024.0f ); - Printf( "%11.1fK %11.1fK Recv rate (wire)\n", info1.m_flInBytesPerSec/1024.0f, info2.m_flInBytesPerSec/1024.0f ); - Printf( "%12.1f %12.1f Recv pkts/sec (wire)\n", info1.m_flInPacketsPerSec, info2.m_flInPacketsPerSec ); - Printf( "%10.1fms %10.1fms Send buffer drain time, based on bandwidth\n", ( info1.m_cbPendingReliable+info1.m_cbPendingUnreliable )*1000.0f/info1.m_nSendRateBytesPerSecond, ( info2.m_cbPendingReliable+info2.m_cbPendingUnreliable )*1000.0f/info2.m_nSendRateBytesPerSecond ); - Printf( "%10.1fms %10.1fms App RTT (reliable)\n", p1.m_flReliableMsgDelay*1e3, p2.m_flReliableMsgDelay*1e3 ); - Printf( "%10.1fms %10.1fms App RTT (unreliable)\n", p1.m_flUnreliableMsgDelay*1e3, p2.m_flUnreliableMsgDelay*1e3 ); + TEST_Printf( "\n" ); + TEST_Printf( "%12s %12s\n", p1.m_sName.c_str(), p2.m_sName.c_str() ); + TEST_Printf( "%10dms %10dms Ping\n", info1.m_nPing, info2.m_nPing ); + TEST_Printf( "%12s %12s Quality\n", FormatQuality( info1.m_flConnectionQualityLocal ).c_str(), FormatQuality( info2.m_flConnectionQualityLocal ).c_str() ); + TEST_Printf( "%11.1fK %11.1fK Send buffer\n", ( info1.m_cbPendingReliable+info1.m_cbPendingUnreliable )/1024.0f, ( info2.m_cbPendingReliable+info2.m_cbPendingUnreliable )/1024.0f ); + TEST_Printf( "%11.1fK %11.1fK Send rate (app)\n", p1.m_flSendRate/1024.0f, p2.m_flSendRate/1024.0f ); + TEST_Printf( "%11.1fK %11.1fK Send rate (wire)\n", info1.m_flOutBytesPerSec/1024.0f, info2.m_flOutBytesPerSec/1024.0f ); + TEST_Printf( "%12.1f %12.1f Send pkts/sec (wire)\n", info1.m_flOutPacketsPerSec, info2.m_flOutPacketsPerSec ); + TEST_Printf( "%11.1fK %11.1fK Send bandwidth (estimate)\n", info1.m_nSendRateBytesPerSecond/1024.0f, info2.m_nSendRateBytesPerSecond/1024.0f ); + TEST_Printf( "%11.1fK %11.1fK Recv rate (app)\n", p1.m_flRecvRate/1024.0f, p2.m_flRecvRate/1024.0f ); + TEST_Printf( "%11.1fK %11.1fK Recv rate (wire)\n", info1.m_flInBytesPerSec/1024.0f, info2.m_flInBytesPerSec/1024.0f ); + TEST_Printf( "%12.1f %12.1f Recv pkts/sec (wire)\n", info1.m_flInPacketsPerSec, info2.m_flInPacketsPerSec ); + TEST_Printf( "%10.1fms %10.1fms Send buffer drain time, based on bandwidth\n", ( info1.m_cbPendingReliable+info1.m_cbPendingUnreliable )*1000.0f/info1.m_nSendRateBytesPerSecond, ( info2.m_cbPendingReliable+info2.m_cbPendingUnreliable )*1000.0f/info2.m_nSendRateBytesPerSecond ); + TEST_Printf( "%10.1fms %10.1fms App RTT (reliable)\n", p1.m_flReliableMsgDelay*1e3, p2.m_flReliableMsgDelay*1e3 ); + TEST_Printf( "%10.1fms %10.1fms App RTT (unreliable)\n", p1.m_flUnreliableMsgDelay*1e3, p2.m_flUnreliableMsgDelay*1e3 ); } static void TestNetworkConditions( int rate, float loss, int lag, float reorderPct, int reorderLag, bool bActLikeGame ) { ISteamNetworkingSockets *pSteamSocketNetworking = SteamNetworkingSockets(); - Printf( "---------------------------------------------------\n" ); - Printf( "NETWORK CONDITIONS\n" ); - Printf( "Rate . . . . . . : %d Bps\n", rate ); - Printf( "Loss . . . . . . : %g%%\n", loss ); - Printf( "Ping . . . . . . : %d\n", lag*2 ); - Printf( "Reorder. . . . . : %g%% @ %dms\n", reorderPct, reorderLag ); - Printf( "Act like game. . : %d\n", (int)bActLikeGame ); - Printf( "---------------------------------------------------\n" ); + TEST_Printf( "---------------------------------------------------\n" ); + TEST_Printf( "NETWORK CONDITIONS\n" ); + TEST_Printf( "Rate . . . . . . : %d Bps\n", rate ); + TEST_Printf( "Loss . . . . . . : %g%%\n", loss ); + TEST_Printf( "Ping . . . . . . : %d\n", lag*2 ); + TEST_Printf( "Reorder. . . . . : %g%% @ %dms\n", reorderPct, reorderLag ); + TEST_Printf( "Act like game. . : %d\n", (int)bActLikeGame ); + TEST_Printf( "---------------------------------------------------\n" ); SteamNetworkingUtils()->SetGlobalConfigValueInt32( k_ESteamNetworkingConfig_SendRateMin, rate ); SteamNetworkingUtils()->SetGlobalConfigValueInt32( k_ESteamNetworkingConfig_SendRateMax, rate ); @@ -470,14 +379,14 @@ static void TestNetworkConditions( int rate, float loss, int lag, float reorderP usecWhenStateEnd = g_usecTestElapsed + ( bQuiet ? usecQuietDuration : usecActiveDuration ); if ( nIterations-- <= 0 ) break; - Printf( "Entering active time (sending enabled)\n" ); + TEST_Printf( "Entering active time (sending enabled)\n" ); } } else { bQuiet = true; usecWhenStateEnd = g_usecTestElapsed + usecQuietDuration; - Printf( "Entering quiet time (no sending) to see how fast queues drain\n" ); + TEST_Printf( "Entering quiet time (no sending) to see how fast queues drain\n" ); } } @@ -553,7 +462,7 @@ static void RunSteamDatagramConnectionTest() // Wait for connection to complete while ( !g_peerClient.m_bIsConnected || !g_peerServer.m_bIsConnected ) - PumpCallbacks(); + TEST_PumpCallbacks(); auto Test = []( int rate, float loss, int lag, float reorderPct, int reorderLag ) { @@ -626,12 +535,13 @@ int main( ) TestSteamNetworkingIdentity(); // Create client and server sockets - InitSteamDatagramConnectionSockets(); + TEST_Init( nullptr ); + SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged( OnSteamNetConnectionStatusChanged ); // Run the test RunSteamDatagramConnectionTest(); - ShutdownSteamDatagramConnectionSockets(); + TEST_Kill(); return 0; } diff --git a/tests/test_p2p.cpp b/tests/test_p2p.cpp new file mode 100644 index 0000000..6a49107 --- /dev/null +++ b/tests/test_p2p.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_common.h" + +#include +#include + + +int main( ) +{ + + // Create client and server sockets + TEST_Init( nullptr ); + //SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged( OnSteamNetConnectionStatusChanged ); + + // Run the test + //RunSteamDatagramConnectionTest(); + + TEST_Kill(); + return 0; +} From b81bdd6ac70542650087413cce4fcb2d3e067090 Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Mon, 31 Aug 2020 14:57:40 -0700 Subject: [PATCH 11/14] P2P test mostly written. I really need to be able to compile the webrtc code with my version of cmake to make further progress on this. --- examples/trivial_signaling_client.cpp | 57 ++++++- examples/trivial_signaling_client.h | 4 +- tests/CMakeLists.txt | 4 +- tests/test_common.cpp | 12 ++ tests/test_common.h | 1 + tests/test_p2p.cpp | 228 +++++++++++++++++++++++++- tests/test_p2p.py | 58 +++++++ 7 files changed, 347 insertions(+), 17 deletions(-) create mode 100755 tests/test_p2p.py diff --git a/examples/trivial_signaling_client.cpp b/examples/trivial_signaling_client.cpp index 7c6be63..8ddc171 100644 --- a/examples/trivial_signaling_client.cpp +++ b/examples/trivial_signaling_client.cpp @@ -10,10 +10,14 @@ #include #include +#include "../tests/test_common.h" + #ifdef POSIX #include #include + #include #include + #include typedef int SOCKET; constexpr SOCKET INVALID_SOCKET = -1; inline void closesocket( SOCKET s ) { close(s); } @@ -114,12 +118,16 @@ class CTrivialSignalingClient : public ITrivialSignalingClient #endif m_sock = socket( m_adrServer.ss_family, sockType, IPPROTO_TCP ); if ( m_sock == INVALID_SOCKET ) + { + TEST_Printf( "socket() failed, error=%d\n", GetSocketError() ); return; + } #ifdef _WIN32 opt = 1; if ( ioctlsocket( sock, FIONBIO, (unsigned long*)&opt ) == -1 ) { CloseSocket(); + TEST_Printf( "ioctlsocket() failed, error=%d\n", GetSocketError() ); return; } #endif @@ -161,6 +169,8 @@ public: { // Socket hosed, or we sent a partial signal. // We need to restart connection + TEST_Printf( "Failed to send %d bytes to trivial signaling server. send() returned %d, errno=%d. Closing and restarting connection.\n", + l, r, GetSocketError() ); CloseSocket(); } } @@ -187,7 +197,7 @@ public: { // Drain the socket into the buffer, and check for reconnecting sockMutex.lock(); - if ( m_sock != INVALID_SOCKET ) + if ( m_sock == INVALID_SOCKET ) { Connect(); } @@ -203,7 +213,10 @@ public: { int e = GetSocketError(); if ( e != EAGAIN && e != EWOULDBLOCK ) + { + TEST_Printf( "Failed to recv from trivial signaling server. recv() returned %d, errno=%d. Closing and restarting connection\n", r, e ); CloseSocket(); + } break; } @@ -241,7 +254,8 @@ public: int l = HexDigitVal( m_sBufferedData[i+1] ); if ( ( h | l ) & ~0xf ) { - assert(false); // Failed hex decode. Not a bug in our code here, but this is just example code, so we'll handle it this way + // Failed hex decode. Not a bug in our code here, but this is just example code, so we'll handle it this way + assert( !"Failed hex decode from signaling server?!" ); goto next_message; } data.push_back( (char)(h<<4 | l ) ); @@ -309,11 +323,40 @@ next_message: }; // Start connecting to the signaling server. -//ITrivialSignalingClient *CreateTrivialSignalingClient( -// const sockaddr *adrServer, size_t adrSize, // Address of the server. -// ISteamNetworkingSockets *pSteamNetworkingSockets, // Where should we send signals when we get them? -// SteamnetworkingErrMsg &errMsg // Error message is retjrned here if we fail -//); +ITrivialSignalingClient *CreateTrivialSignalingClient( + const char *pszServerAddress, // Address of the server. + ISteamNetworkingSockets *pSteamNetworkingSockets, // Where should we send signals when we get them? + SteamNetworkingErrMsg &errMsg // Error message is retjrned here if we fail +) { + + std::string sAddress( pszServerAddress ); + std::string sService; + size_t colon = sAddress.find( ':' ); + if ( colon == std::string::npos ) + { + sService = "10000"; // Default port + } + else + { + sService = sAddress.substr( colon+1 ); + sAddress.erase( colon ); + } + + // Resolve name synchronously + addrinfo *pAddrInfo = nullptr; + int r = getaddrinfo( sAddress.c_str(), sService.c_str(), nullptr, &pAddrInfo ); + if ( r != 0 || pAddrInfo == nullptr ) + { + sprintf( errMsg, "Invalid/unknown server address. getaddrinfo returned %d", r ); + return nullptr; + } + + auto *pClient = new CTrivialSignalingClient( pAddrInfo->ai_addr, pAddrInfo->ai_addrlen, pSteamNetworkingSockets ); + + freeaddrinfo( pAddrInfo ); + + return pClient; +} diff --git a/examples/trivial_signaling_client.h b/examples/trivial_signaling_client.h index 25297f8..af4737e 100644 --- a/examples/trivial_signaling_client.h +++ b/examples/trivial_signaling_client.h @@ -5,7 +5,6 @@ #pragma once #include -struct sockaddr; class ISteamNetworkingSockets; // FIXME - Eventually I intend to add a mechanism to set the default @@ -35,6 +34,7 @@ public: /// Interface to our client. class ITrivialSignalingClient : public ISteamNetworkingCustomSignalingService { +public: /// Poll the server for incoming signals and dispatch them. /// We use polling in this example just to keep it simple. @@ -47,7 +47,7 @@ class ITrivialSignalingClient : public ISteamNetworkingCustomSignalingService // Start connecting to the signaling server. ITrivialSignalingClient *CreateTrivialSignalingClient( - const sockaddr *adrServer, size_t adrSize, // Address of the server. + const char *address, // Address:port ISteamNetworkingSockets *pSteamNetworkingSockets, // Where should we send signals when we get them? SteamNetworkingErrMsg &errMsg // Error message is retjrned here if we fail ); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ce022c8..cda1618 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -33,4 +33,6 @@ add_sanitizers(test_crypto) file(COPY aesgcmtestvectors DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) -# vim: set ts=4 sts=4 sw=4 noet: +# NOTE: This happens when we generate the projects, and really we'd like to actually +# make a build rule to copy it. I don't want to do that work right now. +file(COPY test_p2p.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/tests/test_common.cpp b/tests/test_common.cpp index 7bc263f..a2f52bf 100644 --- a/tests/test_common.cpp +++ b/tests/test_common.cpp @@ -60,6 +60,18 @@ void TEST_Printf( const char *fmt, ... ) DebugOutput( k_ESteamNetworkingSocketsDebugOutputType_Msg, text ); } +void TEST_Fatal( const char *fmt, ... ) +{ + fflush(stdout); + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + fflush(stderr); + exit(1); +} + void TEST_Init( const SteamNetworkingIdentity *pIdentity ) { g_fpLog = fopen( "log.txt", "wt" ); diff --git a/tests/test_common.h b/tests/test_common.h index 78612f6..e36094a 100644 --- a/tests/test_common.h +++ b/tests/test_common.h @@ -5,6 +5,7 @@ struct SteamNetworkingIdentity; extern void TEST_Init(); extern void TEST_Printf( const char *fmt, ... ); +extern void TEST_Fatal( const char *fmt, ... ); extern void TEST_Init( const SteamNetworkingIdentity *pIdentity ); extern void TEST_Kill(); extern void TEST_PumpCallbacks(); diff --git a/tests/test_p2p.cpp b/tests/test_p2p.cpp index 6a49107..dfb5d33 100644 --- a/tests/test_p2p.cpp +++ b/tests/test_p2p.cpp @@ -12,17 +12,231 @@ #include #include +#include "../examples/trivial_signaling_client.h" - -int main( ) +HSteamListenSocket g_hListenSock; +HSteamNetConnection g_hConnection; +enum ETestRole { + k_ETestRole_Undefined, + k_ETestRole_Server, + k_ETestRole_Client, + k_ETestRole_Symmetric, +}; +ETestRole g_eTestRole = k_ETestRole_Undefined; - // Create client and server sockets - TEST_Init( nullptr ); - //SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged( OnSteamNetConnectionStatusChanged ); +int g_nVirtualPortLocal = 0; // Used when listening, and when connecting +int g_nVirtualPortRemote = 0; // Only used when connecting + +void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_t *pInfo ) +{ + // What's the state of the connection? + switch ( pInfo->m_info.m_eState ) + { + case k_ESteamNetworkingConnectionState_ClosedByPeer: + case k_ESteamNetworkingConnectionState_ProblemDetectedLocally: + + TEST_Printf( "[%s] %s, reason %d: %s\n", + pInfo->m_info.m_szConnectionDescription, + ( pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ClosedByPeer ? "closed by peer" : "problem detected locally" ), + pInfo->m_info.m_eEndReason, + pInfo->m_info.m_szEndDebug + ); + + // Close our end + SteamNetworkingSockets()->CloseConnection( pInfo->m_hConn, 0, nullptr, false ); + + if ( g_hConnection == pInfo->m_hConn ) + { + g_hConnection = k_HSteamNetConnection_Invalid; + } + else + { + // Why are we hearing about any another connection? + assert( false ); + } + + break; + + case k_ESteamNetworkingConnectionState_None: + // Notification that a connection was destroyed. (By us, presumably.) + // We don't need this, so ignore it. + break; + + case k_ESteamNetworkingConnectionState_Connecting: + + // Is this a connection we initiated, or one that we are receiving? + if ( g_hListenSock != k_HSteamListenSocket_Invalid && pInfo->m_info.m_hListenSocket == g_hListenSock ) + { + // Somebody's knocking + TEST_Printf( "[%s] Accepting\n", pInfo->m_info.m_szConnectionDescription ); + g_hConnection = pInfo->m_hConn; + SteamNetworkingSockets()->AcceptConnection( pInfo->m_hConn ); + } + else + { + // Note that we will get notification when our own connection that + // we initiate enters this state. + } + break; + + case k_ESteamNetworkingConnectionState_FindingRoute: + // P2P connections will spend a bried time here where they swap addresses + // and try to find a route. + TEST_Printf( "[%s] finding route\n", pInfo->m_info.m_szConnectionDescription ); + break; + + case k_ESteamNetworkingConnectionState_Connected: + // We got fully connected + assert( pInfo->m_hConn == g_hConnection ); // We don't initiate or accept any other connections, so this should be out own connection + TEST_Printf( "[%s] connected\n", pInfo->m_info.m_szConnectionDescription ); + break; + + default: + assert( false ); + break; + } +} + + +int main( int argc, const char **argv ) +{ + SteamNetworkingIdentity identityLocal; identityLocal.Clear(); + SteamNetworkingIdentity identityRemote; identityRemote.Clear(); + const char *pszTrivialSignalingService = "localhost:10000"; + + // Parse the command line + for ( int idxArg = 1 ; idxArg < argc ; ++idxArg ) + { + const char *pszSwitch = argv[idxArg]; + + auto GetArg = [&]() -> const char * { + if ( idxArg + 1 >= argc ) + TEST_Fatal( "Expected argument after %s", pszSwitch ); + return argv[++idxArg]; + }; + auto ParseIdentity = [&]( SteamNetworkingIdentity &x ) { + const char *pszArg = GetArg(); + if ( !x.ParseString( pszArg ) ) + TEST_Fatal( "'%s' is not a valid identity string", pszArg ); + }; + + if ( !strcmp( pszSwitch, "--identity-local" ) ) + ParseIdentity( identityLocal ); + else if ( !strcmp( pszSwitch, "--identity-remote" ) ) + ParseIdentity( identityRemote ); + else if ( !strcmp( pszSwitch, "--signaling-server" ) ) + pszTrivialSignalingService = GetArg(); + else if ( !strcmp( pszSwitch, "--client" ) ) + g_eTestRole = k_ETestRole_Client; + else if ( !strcmp( pszSwitch, "--server" ) ) + g_eTestRole = k_ETestRole_Server; + else if ( !strcmp( pszSwitch, "--symmetric" ) ) + g_eTestRole = k_ETestRole_Symmetric; + else + TEST_Fatal( "Unexpected command line argument '%s'", pszSwitch ); + } + + if ( g_eTestRole == k_ETestRole_Undefined ) + TEST_Fatal( "Must specify test role (--server, --client, or --symmetric" ); + if ( identityLocal.IsInvalid() ) + TEST_Fatal( "Must specify local identity using --identity-local" ); + if ( identityRemote.IsInvalid() && g_eTestRole != k_ETestRole_Server ) + TEST_Fatal( "Must specify remote identity using --identity-remote" ); + + // Initialize library, with the desired local identity + TEST_Init( &identityLocal ); + + // Create the signaling service + SteamNetworkingErrMsg errMsg; + ITrivialSignalingClient *pSignaling = CreateTrivialSignalingClient( pszTrivialSignalingService, SteamNetworkingSockets(), errMsg ); + if ( pSignaling == nullptr ) + TEST_Fatal( "Failed to initializing signaling client. %s", errMsg ); + + SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged( OnSteamNetConnectionStatusChanged ); + + // Create listen socket to receive connections on, unless we are the client + if ( g_eTestRole == k_ETestRole_Server ) + { + TEST_Printf( "Creating listen socket, local virtual port %d\n", g_nVirtualPortLocal ); + g_hListenSock = SteamNetworkingSockets()->CreateListenSocketP2P( g_nVirtualPortLocal, 0, nullptr ); + assert( g_hListenSock != k_HSteamListemSocket_Invalid ); + } + else if ( g_eTestRole == k_ETestRole_Symmetric ) + { + + // Currently you must create a listen socket to use symmetric mode, + // even if you know that you will always create connections "both ways". + // In the future we might try to remove this requirement. It is a bit + // less efficient, since it always triggered the race condition case + // where both sides create their own connections, and then one side + // decides to their theirs away. If we have a listen socket, then + // it can be the case that one peer will receive the incoming connection + // from the other peer, and since he has a listen socket, can save + // the connection, and then implicitly accept it when he initiates his + // own connection. Without the listen socket, if an incoming connection + // request arrives before we have started connecting out, then we are forced + // to ignore it, as the app has given no indication that it desires to + // receive inbound connections at all. + TEST_Printf( "Creating listen socket in symmetric mode, local virtual port %d\n", g_nVirtualPortLocal ); + SteamNetworkingConfigValue_t opt; + opt.SetInt32( k_ESteamNetworkingConfig_SymmetricConnect, 1 ); // << Note we set symmetric mode on the listen socket + g_hListenSock = SteamNetworkingSockets()->CreateListenSocketP2P( g_nVirtualPortLocal, 1, &opt ); + assert( g_hListenSock != k_HSteamListemSocket_Invalid ); + } + + // Begin connecting to peer, unless we are the server + if ( g_eTestRole != k_ETestRole_Server ) + { + std::vector< SteamNetworkingConfigValue_t > vecOpts; + + // If we want the local and virtual port to differ, we must set + // an option. This is a pretty rare use case, and usually not needed. + // The local virtual port is only usually relevant for symmetric + // connections, and then, it almost always matches. Here we are + // just showing in this example code how you could handle this if you + // needed them to differ. + if ( g_nVirtualPortRemote != g_nVirtualPortLocal ) + { + SteamNetworkingConfigValue_t opt; + opt.SetInt32( k_ESteamNetworkingConfig_LocalVirtualPort, g_nVirtualPortLocal ); + vecOpts.push_back( opt ); + } + + // Symmetric mode? Noce that since we created a listen socket on this local + // virtual port and tagged it for symmetric connect mode, any connections + // we create that use the same local virtual port will automatically inherit + // this setting. However, this is really not recommended. It is best to be + // explicit. + if ( g_eTestRole == k_ETestRole_Symmetric ) + { + SteamNetworkingConfigValue_t opt; + opt.SetInt32( k_ESteamNetworkingConfig_SymmetricConnect, 1 ); + vecOpts.push_back( opt ); + TEST_Printf( "Connecting to '%s' in symmetric mode, virtual port %d, from local virtual port %d.\n", + SteamNetworkingIdentityRender( identityRemote ).c_str(), g_nVirtualPortRemote, + g_nVirtualPortLocal ); + } + else + { + TEST_Printf( "Connecting to '%s', virtual port %d, from local virtual port %d.\n", + SteamNetworkingIdentityRender( identityRemote ).c_str(), g_nVirtualPortRemote, + g_nVirtualPortLocal ); + } + + g_hConnection = SteamNetworkingSockets()->ConnectP2P( identityRemote, g_nVirtualPortRemote, (int)vecOpts.size(), vecOpts.data() ); + } + + // Main test loop + for (;;) + { + // Check for incoming signals, and dispatch them + pSignaling->Poll(); + + // Check callbacks + TEST_PumpCallbacks(); + } - // Run the test - //RunSteamDatagramConnectionTest(); TEST_Kill(); return 0; diff --git a/tests/test_p2p.py b/tests/test_p2p.py new file mode 100755 index 0000000..5fa372a --- /dev/null +++ b/tests/test_p2p.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 + +# Test script to run the p2p test. This runs three processes: the two clients, +# and the dummy signaling service. +# +# NOTE: You usually won't run this script from its original location. The +# makefiles will copy it into the same location as the tests and examples + +import subprocess +import threading + +def RunProcess( tag, cmdline, **popen_kwargs ): + + with open( tag + ".log", "wt" ) as log: + + def WriteLn( ln ): + print( "%s> %s" % (tag, ln ) ) + log.write( "%s\n" % ln ) + + WriteLn( "Executing: " + ' '.join( cmdline ) ) + process = subprocess.Popen( cmdline, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, **popen_kwargs ) + process.stdin.close() + while True: + sOutput = process.stdout.readline() + if sOutput: + sOutput = str(sOutput, 'utf-8', 'ignore') + WriteLn( sOutput.rstrip() ) + elif process.poll() is not None: + break + process.wait() + WriteLn( "Exitted with %d" % process.returncode ) + +def StartProcessInThread( tag, cmdline, **popen_kwargs ): + def ThreadProc(): + RunProcess( tag, cmdline, **popen_kwargs ) + thread = threading.Thread( target=ThreadProc, name=tag ) + thread.start() + return thread + +def StartClientInThread( role, local, remote ): + cmdline = [ + "./test_p2p", + "--" + role, + "--identity-local", "str:"+local, + "--identity-remote", "str:"+remote, + "--signaling-server", "localhost:10000" + ] + return StartProcessInThread( local, cmdline ); + +signaling = StartProcessInThread( "signaling", [ './trivial_signaling_server' ], cwd="../examples" ) + +client1 = StartClientInThread( "server", "peer_server", "peer_client" ) +client2 = StartClientInThread( "client", "peer_client", "peer_server" ) + +client1.join() +client2.join() +signaling.join() + From 3d35c23835637cab3ecfa67f15b760cf281409f8 Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Mon, 31 Aug 2020 17:07:57 -0700 Subject: [PATCH 12/14] Move all build output to bin folder. Not scattered throughout the tree. This makes it easier to run tests, etc. --- CMakeLists.txt | 11 +++++++---- examples/CMakeLists.txt | 6 ++---- tests/CMakeLists.txt | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 38e1176..68974c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,11 @@ include(CMakeDependentOption) include(CMakePushCheckState) include(CheckSymbolExists) +# Put all the output from all projects into the same folder +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + project(GameNetworkingSockets C CXX) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) @@ -140,9 +145,7 @@ endif() add_subdirectory(src) -message(STATUS "---------------------------------------------------------") +#message(STATUS "---------------------------------------------------------") message(STATUS "Crypto library for AES/SHA256: ${USE_CRYPTO}") message(STATUS "Crypto library for ed25519/curve25519: ${USE_CRYPTO25519}") -message(STATUS "---------------------------------------------------------") - -# vim: set ts=4 sts=4 sw=4 noet: +#message(STATUS "---------------------------------------------------------") diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2b4f9f3..196f16b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -7,7 +7,7 @@ find_library(golang REQUIRED) # Trivial signaling server, written in go # set(SIGNAL_SERVER_TARGET trivial_signaling_server) -set(SIGNAL_SERVER_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/trivial_signaling_server) +set(SIGNAL_SERVER_OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/trivial_signaling_server) set(SIGNAL_SERVER_SRCS trivial_signaling_server.go ) @@ -18,9 +18,7 @@ add_custom_command( COMMENT "Building GO Trivial signaling server" COMMAND env go build -o "${SIGNAL_SERVER_OUTPUT}" ${CMAKE_GO_FLAGS} ${SIGNAL_SERVER_SRCS} ) -add_custom_target(${SIGNAL_SERVER_TARGET}_all ALL DEPENDS ${SIGNAL_SERVER_OUTPUT}) -#add_custom_target(${SIGNAL_SERVER_TARGET} ALL) -#add_custom_target(${SIGNAL_SERVER_OUTPUT} ALL) +add_custom_target(${SIGNAL_SERVER_TARGET} ALL DEPENDS ${SIGNAL_SERVER_OUTPUT}) # # Really simple client/server chat diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cda1618..eea2395 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -31,8 +31,8 @@ target_include_directories(test_crypto PRIVATE ../src ../src/public ../src/commo target_link_libraries(test_crypto GameNetworkingSockets_s) add_sanitizers(test_crypto) -file(COPY aesgcmtestvectors DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +file(COPY aesgcmtestvectors DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) # NOTE: This happens when we generate the projects, and really we'd like to actually # make a build rule to copy it. I don't want to do that work right now. -file(COPY test_p2p.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +file(COPY test_p2p.py DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) From 174d27e707c9df8535a039d4271e4345f832b3ec Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Mon, 31 Aug 2020 17:16:29 -0700 Subject: [PATCH 13/14] Drop meson support I don't want to learn or maintain two build systems. cmake is the javascript of cross-platform build systems: Yes, it's weird and old and crusty and you can point out all sorts of problems with it. But it's also what the community is consolidating on. --- .travis/build-meson.sh | 48 ------------ .travis/build.sh | 12 +-- BUILDING.md | 23 ++---- examples/meson.build | 12 --- meson.build | 121 ------------------------------ meson_options.txt | 21 ------ src/meson.build | 165 ----------------------------------------- tests/meson.build | 39 ---------- 8 files changed, 12 insertions(+), 429 deletions(-) delete mode 100755 .travis/build-meson.sh delete mode 100644 examples/meson.build delete mode 100644 meson.build delete mode 100644 meson_options.txt delete mode 100644 src/meson.build delete mode 100644 tests/meson.build diff --git a/.travis/build-meson.sh b/.travis/build-meson.sh deleted file mode 100755 index 66bf0c6..0000000 --- a/.travis/build-meson.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash -# -# Meson build test -# - -set -e - -cleanup() { - echo "Cleaning up Meson build directories" >&2 - rm -rf build-meson{,-ref,-sodium{,25519}} -} - -trap cleanup EXIT -cleanup - -MESON_ARGS=( - -Dlight_tests=true - -DWerror=true -) - -BUILD_LIBSODIUM=1 - -# libsodium's AES implementation only works on x86_64 -[[ $(uname -m) != x86_64 ]] && BUILD_LIBSODIUM=0 - -set -x - -# Build lightweight test builds -meson . build-meson ${MESON_ARGS[@]} --buildtype debugoptimized -meson . build-meson-ref ${MESON_ARGS[@]} --buildtype debugoptimized -Duse_crypto25519=Reference -[[ $BUILD_LIBSODIUM -ne 0 ]] && meson . build-meson-sodium ${MESON_ARGS[@]} --buildtype debugoptimized -Duse_crypto=libsodium -Duse_crypto25519=libsodium -meson . build-meson-sodium25519 ${MESON_ARGS[@]} --buildtype debugoptimized -Duse_crypto25519=libsodium - -# Build all targets -ninja -v -C build-meson -[[ $BUILD_LIBSODIUM -ne 0 ]] && ninja -v -C build-meson-sodium -ninja -v -C build-meson-sodium25519 -ninja -v -C build-meson-ref - -# Run basic tests -build-meson/tests/test_crypto -[[ $BUILD_LIBSODIUM -ne 0 ]] && build-meson-sodium/tests/test_crypto -build-meson-sodium25519/tests/test_crypto -build-meson-ref/tests/test_crypto - -set +x - -exit 0 diff --git a/.travis/build.sh b/.travis/build.sh index 3e9ab50..8602789 100755 --- a/.travis/build.sh +++ b/.travis/build.sh @@ -52,8 +52,8 @@ fi msg "Image is $IMAGE:$IMAGE_TAG, sanitizers enabled: $BUILD_SANITIZERS" -# We need at least one build system available -has cmake || has meson || die "No build system available" +# Make sure cmake is installed +has cmake || die "cmake required" # We also need at least one compiler has_clang || has_gcc || die "No compiler available" @@ -63,16 +63,16 @@ ccache -M4G if has_clang; then msg "Beginning build tests with Clang" - export CC="ccache clang" CXX="ccache clang++" - has meson && bash .travis/build-meson.sh + #export CC="ccache clang" CXX="ccache clang++" + #has meson && bash .travis/build-meson.sh export CC=clang CXX=clang++ has cmake && bash .travis/build-cmake.sh fi if has_gcc; then msg "Beginning build tests with GCC" - export CC="ccache gcc" CXX="ccache g++" - has meson && bash .travis/build-meson.sh + #export CC="ccache gcc" CXX="ccache g++" + #has meson && bash .travis/build-meson.sh export CC=gcc CXX=g++ has cmake && bash .travis/build-cmake.sh fi diff --git a/BUILDING.md b/BUILDING.md index 9720145..cabb493 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -3,7 +3,8 @@ Building ## Dependencies -* CMake or Meson, and build tool like Ninja, GNU Make or Visual Studio +* CMake 3.10 or later +* A build tool like Ninja, GNU Make or Visual Studio * A C++11-compliant compiler, such as: * GCC 7.3 or later * Clang 3.3 or later @@ -51,13 +52,6 @@ $ cmake -G Ninja .. $ ninja ``` -Or Meson (supported...kind-of): - -``` -$ meson . build -$ ninja -C build -``` - ## Windows / Visual Studio On Windows, you can use the [vcpkg](https://github.com/microsoft/vcpkg/) package manager. @@ -229,7 +223,6 @@ a 32-bit build, install the i686 versions of these packages): $ pacman -S \ git \ mingw-w64-x86_64-gcc \ - mingw-w64-x86_64-meson \ mingw-w64-x86_64-openssl \ mingw-w64-x86_64-pkg-config \ mingw-w64-x86_64-protobuf @@ -240,8 +233,10 @@ And finally, clone the repository and build it: ``` $ git clone https://github.com/ValveSoftware/GameNetworkingSockets.git $ cd GameNetworkingSockets -$ meson . build -$ ninja -C build +$ mkdir build +$ cd build +$ cmake -G Ninja .. +$ ninja ``` **NOTE:** When building with MSYS2, be sure you launch the correct version of @@ -267,9 +262,3 @@ This extension allows for configuring the CMake project and building it from within the Visual Studio Code IDE. VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=vector-of-bool.cmake-tools - -### Meson by Ali Sabil -This extension comes in handy if you're editing the Meson build files. - -VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=asabil.meson - diff --git a/examples/meson.build b/examples/meson.build deleted file mode 100644 index 5409767..0000000 --- a/examples/meson.build +++ /dev/null @@ -1,12 +0,0 @@ -cxx_compiler = meson.get_compiler('cpp') - -dependencies = [ - dep_threads, - dep_GameNetworkingSockets_so # Declared in the other project -] - -executable('example_chat', - 'example_chat.cpp', - dependencies: dependencies, -) - diff --git a/meson.build b/meson.build deleted file mode 100644 index 1a5e4d3..0000000 --- a/meson.build +++ /dev/null @@ -1,121 +0,0 @@ -project('GameNetworkingSockets', 'cpp', 'c', - default_options: [ - 'buildtype=debugoptimized', - ], - license: 'BSD' -) - -flags_common = [ - '-fno-strict-aliasing', - '-fvisibility=hidden', -] - -target_os = target_machine.system() -if target_os == 'windows' - flags_common += [ - '-fno-stack-protector', - ] -else - flags_common += [ - '-fstack-protector-strong', - '-fstack-clash-protection', - ] -endif - -flags_cxx = [ - '-fno-rtti', - '-fno-exceptions', - '-fvisibility-inlines-hidden', -] - -warn_flags_common = [ - '-Wall', - '-Wextra', - - # Explicitly disable noisy warnings - '-Wno-documentation', - '-Wno-implicit-fallthrough', - '-Wno-padded', - '-Wno-sign-compare', - '-Wno-sign-conversion', - '-Wno-signed-enum-bitfield', - '-Wno-unknown-pragmas', - '-Wno-unused-const-variable', - '-Wno-unused-local-typedef', - '-Wno-unused-parameter', - '-Wno-nested-anon-types', - '-Wno-format-truncation' -] - -werror = get_option('Werror') -if werror - warn_flags_common += [ '-Werror' ] -endif - -use_crypto_25519 = get_option('use_crypto25519') -if use_crypto_25519 == 'Reference' - warn_flags_common += [ '-Wno-unused-function' ] -endif - -warn_flags_c = [ - '-Wimplicit', - '-Wstrict-prototypes', - '-Wno-missing-prototypes', -] - -warn_flags_cxx = [ - '-Wno-c++98-compat', - '-Wno-c++98-compat-pedantic', - '-Wno-exit-time-destructors', - '-Wno-global-constructors', - '-Wno-non-virtual-dtor', - '-Wno-old-style-cast', - '-Wno-reorder', - - # These ones are prolific but don't really matter. Most are in generated - # protobuf code. - '-Wno-zero-as-null-pointer-constant', - '-Wno-missing-variable-declarations', -] - -c_compiler = meson.get_compiler('c') -cxx_compiler = meson.get_compiler('cpp') - -c_flags = [] -c_flags += c_compiler.first_supported_argument(['-std=c11', '-std=gnu99']) -foreach arg : flags_common + warn_flags_common + warn_flags_c - if c_compiler.has_argument(arg) - c_flags += [ arg ] - endif -endforeach -add_project_arguments(c_flags, language: 'c') - -cxx_flags = [] -cxx_flags += cxx_compiler.first_supported_argument(['-std=c++11', '-std=c++0x']) -foreach arg : flags_common + warn_flags_common + flags_cxx + warn_flags_cxx - if cxx_compiler.has_argument(arg) - cxx_flags += [ arg ] - endif -endforeach -add_project_arguments(cxx_flags, language: 'cpp') - -# Preprocessor defines for platform and compiler -cpp_flags = [] -if target_os == 'linux' - cpp_flags += ['-DPOSIX', '-DLINUX'] -elif target_os == 'darwin' - cpp_flags += ['-DPOSIX', '-DOSX'] -elif target_os == 'windows' - cpp_flags += ['-D_WIN32', '-DWIN32', '-D__STDC_FORMAT_MACROS=1', '-D__USE_MINGW_ANSI_STDIO=0'] -else - error('Could not identify your target operating system') -endif - -add_project_arguments(cpp_flags, language: 'c') -add_project_arguments(cpp_flags, language: 'cpp') - -dep_threads = dependency('threads') - -subdir('src') -subdir('tests') -subdir('examples') diff --git a/meson_options.txt b/meson_options.txt deleted file mode 100644 index b48e26a..0000000 --- a/meson_options.txt +++ /dev/null @@ -1,21 +0,0 @@ -option('light_tests', - type: 'boolean', - value: true, - description: 'Use smaller/shorter tests for simple integration testing (e.g. Travis)') - -option('Werror', - type: 'boolean', - value: false, - description: 'Enable -Werror flag when compiling') - -option('use_crypto', - type: 'combo', - value: 'OpenSSL', - description: 'Crypto library to use for AES/SHA256', - choices: ['OpenSSL', 'libsodium', 'BCrypt']) - -option('use_crypto25519', - type: 'combo', - value: 'OpenSSL', - description: 'Crypto library to use for ed25519/curve25519', - choices: ['OpenSSL', 'libsodium', 'Reference']) diff --git a/src/meson.build b/src/meson.build deleted file mode 100644 index a042db2..0000000 --- a/src/meson.build +++ /dev/null @@ -1,165 +0,0 @@ -dependencies = [] - -c_compiler = meson.get_compiler('c') - -protoc_bin = find_program('protoc') -protoc = generator(protoc_bin, - output : ['@BASENAME@.pb.cc', '@BASENAME@.pb.h'], - arguments : ['-I@CURRENT_SOURCE_DIR@/common', '--proto_path=@CURRENT_SOURCE_DIR@', '--cpp_out=@BUILD_DIR@', '@INPUT@']) - -use_crypto = get_option('use_crypto') -use_crypto25519 = get_option('use_crypto25519') - -if use_crypto == 'OpenSSL' or use_crypto25519 == 'OpenSSL' - min_libcrypto = dependency('libcrypto', version: '>=1.1.0') - good_libcrypto = dependency('libcrypto', version: '>=1.1.1', required: false) - - if good_libcrypto.found() - dependencies += [ good_libcrypto ] - else - dependencies += [ min_libcrypto ] - endif - - if use_crypto25519 == 'OpenSSL' and not good_libcrypto.found() - error('This version of OpenSSL does not support ed25519/curve25519. Please use -Duse_crypto25519=Reference or upgrade OpenSSL to 1.1.1 or later.') - endif - - code = '''#include - int main(int argc, char **argv) { - EVP_MD_CTX_free(NULL); - return 0; - } - ''' - result = c_compiler.links(code, dependencies: dependencies, name: 'EVP API') - if not result - error('Your OpenSSL version appears to be too old. Check that you\'re using OpenSSL 1.1.0 or later.') - endif -endif - -if use_crypto == 'libsodium' or use_crypto25519 == 'libsodium' - dependencies += [ dependency('libsodium') ] -endif - -dependencies += [ - dep_threads, - dependency('protobuf', version: '>=3.0.0'), -] - -incdirs = include_directories('.', '../include', 'common', 'public') - -cpp_flags = [ - '-DSTEAMNETWORKINGSOCKETS_FOREXPORT', - '-DVALVE_CRYPTO_ENABLE_25519', - '-DCRYPTO_DISABLE_ENCRYPT_WITH_PASSWORD', - '-DGOOGLE_PROTOBUF_NO_RTTI', -] - -target_os = target_machine.system() - -if target_os == 'windows' - dependencies += [ - c_compiler.find_library('ws2_32'), - ] -endif - -protobuf_sources = [ - 'common/steamnetworkingsockets_messages_certs.proto', - 'common/steamnetworkingsockets_messages.proto', - 'common/steamnetworkingsockets_messages_udp.proto', -] - -sources = [ - 'common/crypto_textencode.cpp', - 'common/keypair.cpp', - 'common/steamid.cpp', - 'steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp', - 'steamnetworkingsockets/clientlib/steamnetworkingsockets_flat.cpp', - 'steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.cpp', - 'steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp', - 'steamnetworkingsockets/clientlib/steamnetworkingsockets_p2p.cpp', - 'steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp', - 'steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.cpp', - 'steamnetworkingsockets/steamnetworkingsockets_certs.cpp', - 'steamnetworkingsockets/steamnetworkingsockets_certstore.cpp', - 'steamnetworkingsockets/steamnetworkingsockets_shared.cpp', - 'steamnetworkingsockets/steamnetworkingsockets_stats.cpp', - 'steamnetworkingsockets/steamnetworkingsockets_thinker.cpp', - 'tier0/dbg.cpp', - 'tier0/platformtime.cpp', - 'tier1/bitstring.cpp', - 'tier1/ipv6text.c', - 'tier1/netadr.cpp', - 'tier1/utlbuffer.cpp', - 'tier1/utlmemory.cpp', - 'vstdlib/strtools.cpp', -] - -if use_crypto == 'OpenSSL' - cpp_flags += [ '-DSTEAMNETWORKINGSOCKETS_CRYPTO_VALVEOPENSSL' ] - sources += [ - 'common/opensslwrapper.cpp', - 'common/crypto_openssl.cpp', - ] -endif - -if use_crypto == 'BCrypt' - dependencies += [ c_compiler.find_library('bcrypt') ] - cpp_flags += [ '-DED25519_HASH_BCRYPT', '-DSTEAMNETWORKINGSOCKETS_CRYPTO_BCRYPT' ] - sources += [ 'common/crypto_bcrypt.cpp' ] -endif - -if use_crypto == 'libsodium' - if target_machine.cpu_family() != 'x86' and target_machine.cpu_family() != 'x86_64' - error('-Duse_crypto=libsodium invalid, libsodium AES implementation only works on x86/x86_64 CPUs') - endif - cpp_flags += [ '-DSTEAMNETWORKINGSOCKETS_CRYPTO_LIBSODIUM' ] - sources += [ 'common/crypto_libsodium.cpp' ] -endif - -if use_crypto25519 == 'OpenSSL' - cpp_flags += [ '-DSTEAMNETWORKINGSOCKETS_CRYPTO_25519_OPENSSL' ] - sources += [ 'common/crypto_25519_openssl.cpp' ] -endif - -if use_crypto25519 == 'libsodium' - cpp_flags += [ '-DSTEAMNETWORKINGSOCKETS_CRYPTO_25519_LIBSODIUM' ] - sources += [ 'common/crypto_25519_libsodium.cpp' ] -endif - -if use_crypto25519 == 'Reference' - cpp_flags += [ '-DVALVE_CRYPTO_25519_DONNA' ] - sources += [ - 'common/crypto_25519_donna.cpp', - 'external/curve25519-donna/curve25519.c', - 'external/curve25519-donna/curve25519_VALVE_sse2.c', - 'external/ed25519-donna/ed25519_VALVE.c', - 'external/ed25519-donna/ed25519_VALVE_sse2.c', - ] -endif - -protobufs = protoc.process(protobuf_sources) - -GameNetworkingSockets_static = static_library('GameNetworkingSockets', - sources, protobufs, - c_args: cpp_flags, - cpp_args: cpp_flags, - include_directories: incdirs, - dependencies: dependencies) - -dep_GameNetworkingSockets_static = declare_dependency( - include_directories: include_directories( '../include', '.' ), - compile_args: ['-DSTEAMNETWORKINGSOCKETS_STATIC_LINK'], - link_with: GameNetworkingSockets_static ) - -GameNetworkingSockets_so = library('GameNetworkingSockets', - sources, protobufs, - c_args: cpp_flags, - cpp_args: cpp_flags, - include_directories: incdirs, - dependencies: dependencies) - -dep_GameNetworkingSockets_so = declare_dependency( - include_directories: include_directories( '../include', '.' ), - link_with: GameNetworkingSockets_so ) - -# vim: set ts=2 sts=2 sw=2 et: diff --git a/tests/meson.build b/tests/meson.build deleted file mode 100644 index 7369e65..0000000 --- a/tests/meson.build +++ /dev/null @@ -1,39 +0,0 @@ -common_deps = [ dep_threads ] -cppflags = [ - '-DVALVE_CRYPTO_ENABLE_25519' -] - -light_tests= get_option('light_tests') -if light_tests - cppflags += ['-DLIGHT_TESTS'] -endif - -executable('test_connection', - 'test_connection.cpp', - dependencies: common_deps + [ dep_GameNetworkingSockets_so ], - cpp_args: cppflags, -) - -executable('test_crypto', - 'test_crypto.cpp', - dependencies: common_deps + [ dep_GameNetworkingSockets_static ], - cpp_args: cppflags + ['-DTEST_VECTOR_DIR="' + join_paths(meson.source_root(), 'tests/aesgcmtestvectors') + '/"'], - include_directories: include_directories('../src', '../src/public', '../src/common') -) - -# !FIXME! Ug cannot link with the static lib, because we need to #define the hardcoded key. -# So we'll need the crypto and protobuf dependencies, and those are pretty complicated. -# We need to refactor these files to get that organized. -#executable('test_pki', -# 'test_pki.cpp', -# dependencies: common_deps, -# cpp_args: cppflags + ['-DSTEAMNETWORKINGSOCKETS_HARDCODED_ROOT_CA_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMPsJA3FUaQSsHkyYhWvcYt9v3XbwRBcWuVuayWGdK1"'], -# include_directories: include_directories('../src', '../include', '../src/public', '../src/common') -#) - -#incdirs = include_directories('../include') -#executable('test_flat', -# 'test_flat.c', -# dependence: dependencies, -# include_directories: incdirs -#) From aa6c92de2502a77adbd2025ebf85545264efc8e4 Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Mon, 31 Aug 2020 17:22:12 -0700 Subject: [PATCH 14/14] Fix test script after directory shuffle --- .travis/build-cmake.sh | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.travis/build-cmake.sh b/.travis/build-cmake.sh index 718aefc..1b5c35e 100755 --- a/.travis/build-cmake.sh +++ b/.travis/build-cmake.sh @@ -99,21 +99,23 @@ if [[ $BUILD_SANITIZERS -ne 0 ]]; then fi # Run basic tests -build-cmake-ref/tests/test_crypto -[[ $BUILD_LIBSODIUM -ne 0 ]] && build-cmake-sodium/tests/test_crypto -build-cmake-sodium25519/tests/test_crypto -build-cmake/tests/test_crypto -build-cmake/tests/test_connection +build-cmake-ref/bin/test_crypto +[[ $BUILD_LIBSODIUM -ne 0 ]] && build-cmake-sodium/bin/test_crypto +build-cmake-sodium25519/bin/test_crypto +build-cmake/bin/test_crypto +build-cmake/bin/test_connection # Run sanitized builds if [[ $BUILD_SANITIZERS -ne 0 ]]; then for SANITIZER in asan ubsan tsan; do [[ -d build-${SANITIZER} ]] || continue - build-${SANITIZER}/tests/test_crypto - build-${SANITIZER}/tests/test_connection + build-${SANITIZER}/bin/test_crypto + build-${SANITIZER}/bin/test_connection done fi +# FIXME Run P2P tests? + set +x exit 0