using System; using System.Collections; using System.Collections.Generic; using System.Text; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests { [TestFixture(PlayerCreation.Prefab)] [TestFixture(PlayerCreation.PrefabHash)] [TestFixture(PlayerCreation.NoPlayer)] [TestFixture(PlayerCreation.FailValidation)] internal class ConnectionApprovalTests : IntegrationTestWithApproximation { private const string k_InvalidToken = "Invalid validation token!"; public enum PlayerCreation { Prefab, PrefabHash, NoPlayer, FailValidation } private PlayerCreation m_PlayerCreation; private bool m_ClientDisconnectReasonValidated; private Vector3 m_ExpectedPosition; private Quaternion m_ExpectedRotation; private Dictionary m_Validated = new Dictionary(); public ConnectionApprovalTests(PlayerCreation playerCreation) { m_PlayerCreation = playerCreation; } protected override int NumberOfClients => 1; private Guid m_ValidationToken; protected override bool ShouldCheckForSpawnedPlayers() { return m_PlayerCreation != PlayerCreation.NoPlayer; } protected override void OnServerAndClientsCreated() { if (m_PlayerCreation == PlayerCreation.Prefab || m_PlayerCreation == PlayerCreation.PrefabHash) { m_ExpectedPosition = GetRandomVector3(-10.0f, 10.0f); m_ExpectedRotation = Quaternion.Euler(GetRandomVector3(-359.98f, 359.98f)); } m_ClientDisconnectReasonValidated = false; m_BypassConnectionTimeout = m_PlayerCreation == PlayerCreation.FailValidation; m_Validated.Clear(); m_ValidationToken = Guid.NewGuid(); var validationToken = Encoding.UTF8.GetBytes(m_ValidationToken.ToString()); m_ServerNetworkManager.ConnectionApprovalCallback = NetworkManagerObject_ConnectionApprovalCallback; m_ServerNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerCreation == PlayerCreation.Prefab ? m_PlayerPrefab : null; if (m_PlayerCreation == PlayerCreation.PrefabHash) { m_ServerNetworkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = m_PlayerPrefab }); } m_ServerNetworkManager.NetworkConfig.ConnectionApproval = true; m_ServerNetworkManager.NetworkConfig.ConnectionData = validationToken; foreach (var client in m_ClientNetworkManagers) { client.NetworkConfig.PlayerPrefab = m_PlayerCreation == PlayerCreation.Prefab ? m_PlayerPrefab : null; if (m_PlayerCreation == PlayerCreation.PrefabHash) { client.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = m_PlayerPrefab }); } client.NetworkConfig.ConnectionApproval = true; client.NetworkConfig.ConnectionData = m_PlayerCreation == PlayerCreation.FailValidation ? Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()) : validationToken; if (m_PlayerCreation == PlayerCreation.FailValidation) { client.OnClientDisconnectCallback += Client_OnClientDisconnectCallback; } } base.OnServerAndClientsCreated(); } private void Client_OnClientDisconnectCallback(ulong clientId) { m_ClientNetworkManagers[0].OnClientDisconnectCallback -= Client_OnClientDisconnectCallback; m_ClientDisconnectReasonValidated = m_ClientNetworkManagers[0].LocalClientId == clientId && m_ClientNetworkManagers[0].DisconnectReason == k_InvalidToken; } private bool ClientAndHostValidated() { if (!m_Validated.ContainsKey(m_ServerNetworkManager.LocalClientId) || !m_Validated[m_ServerNetworkManager.LocalClientId]) { return false; } if (m_PlayerCreation == PlayerCreation.FailValidation) { return m_ClientDisconnectReasonValidated; } else { foreach (var client in m_ClientNetworkManagers) { if (!m_Validated.ContainsKey(client.LocalClientId) || !m_Validated[client.LocalClientId]) { return false; } } } return true; } private bool ValidatePlayersPositionRotation() { foreach (var playerEntries in m_PlayerNetworkObjects) { foreach (var player in playerEntries.Value) { if (!Approximately(player.Value.transform.position, m_ExpectedPosition)) { return false; } if (!Approximately(player.Value.transform.rotation, m_ExpectedRotation)) { return false; } } } return true; } [UnityTest] public IEnumerator ConnectionApproval() { yield return WaitForConditionOrTimeOut(ClientAndHostValidated); AssertOnTimeout("Timed out waiting for all clients to be approved!"); if (m_PlayerCreation == PlayerCreation.Prefab || m_PlayerCreation == PlayerCreation.PrefabHash) { yield return WaitForConditionOrTimeOut(ValidatePlayersPositionRotation); AssertOnTimeout("Not all player prefabs spawned in the correct position and/or rotation!"); } } private void NetworkManagerObject_ConnectionApprovalCallback(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response) { var stringGuid = Encoding.UTF8.GetString(request.Payload); if (m_ValidationToken.ToString() == stringGuid) { m_Validated.Add(request.ClientNetworkId, true); response.Approved = true; } else { response.Approved = false; response.Reason = "Invalid validation token!"; } response.CreatePlayerObject = ShouldCheckForSpawnedPlayers(); response.Position = m_ExpectedPosition; response.Rotation = m_ExpectedRotation; response.PlayerPrefabHash = m_PlayerCreation == PlayerCreation.PrefabHash ? m_PlayerPrefab.GetComponent().GlobalObjectIdHash : null; } [Test] public void VerifyUniqueNetworkConfigPerRequest() { var networkConfig = new NetworkConfig { EnableSceneManagement = true, TickRate = 30 }; var currentHash = networkConfig.GetConfig(); networkConfig.EnableSceneManagement = false; networkConfig.TickRate = 60; var newHash = networkConfig.GetConfig(false); Assert.True(currentHash != newHash, $"Hashed {nameof(NetworkConfig)} values {currentHash} and {newHash} should not be the same!"); } } }