See More

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.Threading; using NetcodeIO.NET.Utils; using NetcodeIO.NET.Utils.IO; using NetcodeIO.NET.Internal; using System.Diagnostics; namespace NetcodeIO.NET.Tests { ///

/// A suite of test methods for verifying that components of the API functions as intended /// All methods will either throw an exception if the test fails, or will simply return with no errors on success. /// public static class Tests { const ulong TEST_PROTOCOL_ID = 0x1122334455667788L; const ulong TEST_CLIENT_ID = 0x1L; const int TEST_SERVER_PORT = 40000; const int TEST_CONNECT_TOKEN_EXPIRY = 30; static readonly byte[] _privateKey = new byte[] { 0x60, 0x6a, 0xbe, 0x6e, 0xc9, 0x19, 0x10, 0xea, 0x9a, 0x65, 0x62, 0xf6, 0x6f, 0x2b, 0x30, 0xe4, 0x43, 0x71, 0xd6, 0x2c, 0xd1, 0x99, 0x27, 0x26, 0x6b, 0x3c, 0x60, 0xf4, 0xb7, 0x15, 0xab, 0xa1, }; private struct testEncryptionMapping { public EndPoint address; public byte[] SendKey; public byte[] ReceiveKey; public int TimeoutSeconds; public uint ClientID; } public static void TestSequence() { assert(MiscUtils.SequenceBytesRequired(0) == 1); assert(MiscUtils.SequenceBytesRequired(0x11) == 1); assert(MiscUtils.SequenceBytesRequired(0x1122) == 2); assert(MiscUtils.SequenceBytesRequired(0x112233) == 3); assert(MiscUtils.SequenceBytesRequired(0x11223344) == 4); assert(MiscUtils.SequenceBytesRequired(0x1122334455) == 5); assert(MiscUtils.SequenceBytesRequired(0x112233445566) == 6); assert(MiscUtils.SequenceBytesRequired(0x11223344556677) == 7); assert(MiscUtils.SequenceBytesRequired(0x1122334455667788) == 8); } public static void TestConnectToken() { TokenFactory factory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); IPEndPoint serverEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT); IPEndPoint serverEndpoint2 = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT + 10); IPEndPoint[] connectServers = new IPEndPoint[] { serverEndpoint, serverEndpoint2 }; byte[] userData = new byte[256]; KeyUtils.GenerateKey(userData); byte[] nonce = new byte[Defines.NETCODE_CONNECT_TOKEN_NONCE_BYTES]; KeyUtils.GenerateKey(nonce); ulong timestamp = DateTime.Now.ToUnixTimestamp(); byte[] connectToken = factory.GenerateConnectToken(connectServers, TEST_CONNECT_TOKEN_EXPIRY, 5, nonce, TEST_CLIENT_ID, userData); NetcodePublicConnectToken token = new NetcodePublicConnectToken(); using (var reader = ByteArrayReaderWriter.Get(connectToken)) { token.Read(reader); } assert(token.ProtocolID == TEST_PROTOCOL_ID, "Token protocol ID does not match"); assert(token.TimeoutSeconds == 5, "Token expiry does not match"); assert(token.ConnectServers.Length == connectServers.Length, "Token connect server lists do not match"); for (int i = 0; i < token.ConnectServers.Length; i++) { bool match = token.ConnectServers[i].Endpoint.Equals(connectServers[i]); assert(match, "Token connect server lists do not match"); } assert(token.ExpireTimestamp > token.CreateTimestamp, "Token expire timestamp invalid"); assert(token.CreateTimestamp >= timestamp, "Token create timestamp invalid"); byte[] privateConnectToken = token.PrivateConnectTokenBytes; NetcodePrivateConnectToken privateToken = new NetcodePrivateConnectToken(); //privateToken.Read(privateConnectToken, _privateKey, TEST_PROTOCOL_ID, token.ExpireTimestamp, token.ConnectTokenSequence); assert(privateToken.ClientID == TEST_CLIENT_ID, "Private token client ID does not match"); assert(MiscUtils.ByteArraysEqual(userData, privateToken.UserData), "Private token user data mismatch"); } public static void TestChallengeToken() { // generate a challenge token NetcodeChallengeToken challengeToken = new NetcodeChallengeToken(); challengeToken.ClientID = TEST_CLIENT_ID; byte[] userData = new byte[256]; KeyUtils.GenerateKey(userData); challengeToken.UserData = userData; // write challenge token byte[] challengeTokenBytes = new byte[300]; using (var writer = ByteArrayReaderWriter.Get(challengeTokenBytes)) challengeToken.Write(writer); // encrypt buffer byte[] encryptedChallengeToken = new byte[300]; ulong sequence = 1000; byte[] key = new byte[32]; KeyUtils.GenerateKey(key); int challengeTokenLength = PacketIO.EncryptChallengeToken(sequence, challengeTokenBytes, key, encryptedChallengeToken); // read challenge token back in NetcodeChallengeToken readChallengeToken = new NetcodeChallengeToken(); readChallengeToken.Read(encryptedChallengeToken, sequence, key); assert(readChallengeToken.ClientID == challengeToken.ClientID, "Client ID mismatch"); assert(MiscUtils.ByteArraysEqual(readChallengeToken.UserData, challengeToken.UserData), "User data mismatch"); } public static void TestEncryptionManager() { EncryptionManager encryptionManager = new EncryptionManager(256); encryptionManager.Reset(); double time = 100.0; testEncryptionMapping[] encryptionMapping = new testEncryptionMapping[5]; for (int i = 0; i < encryptionMapping.Length; i++) { encryptionMapping[i] = new testEncryptionMapping() { address = new IPEndPoint(IPAddress.Parse("0:0:0:0:0:0:0:1"), 20000 + i), SendKey = new byte[32], ReceiveKey = new byte[32], TimeoutSeconds = 10 + i, ClientID = (uint)i }; KeyUtils.GenerateKey(encryptionMapping[i].SendKey); KeyUtils.GenerateKey(encryptionMapping[i].ReceiveKey); } // add encryption mappings and make sure they can be looked up by address for (int i = 0; i < encryptionMapping.Length; i++) { int encryptionIndex = encryptionManager.FindEncryptionMapping(encryptionMapping[i].address, time); assert(encryptionIndex == -1, "Encryption manager returned invalid index"); assert(encryptionManager.GetSendKey(encryptionIndex) == null, "Encryption manager returned invalid key"); assert(encryptionManager.GetReceiveKey(encryptionIndex) == null, "Encryption manager returned invalid key"); assert(encryptionManager.AddEncryptionMapping(encryptionMapping[i].address, encryptionMapping[i].SendKey, encryptionMapping[i].ReceiveKey, time, -1.0, encryptionMapping[i].TimeoutSeconds, encryptionMapping[i].ClientID), "Encryption manager failed to add mapping"); encryptionIndex = encryptionManager.FindEncryptionMapping(encryptionMapping[i].address, time); int timeoutSeconds = encryptionManager.GetTimeoutSeconds(encryptionIndex); uint clientID = encryptionManager.GetClientID(encryptionIndex); assert(timeoutSeconds == encryptionMapping[i].TimeoutSeconds, "Encryption manager returned invalid timeout seconds"); assert(clientID == encryptionMapping[i].ClientID, "Encryption manager returned invalid client ID"); byte[] sendKey = encryptionManager.GetSendKey(encryptionIndex); byte[] receiveKey = encryptionManager.GetReceiveKey(encryptionIndex); assert(sendKey != null, "Encryption manager failed to return key"); assert(receiveKey != null, "Encryption manager failed to return key"); assert(MiscUtils.ByteArraysEqual(sendKey, encryptionMapping[i].SendKey), "Encryption manager returned invalid key"); assert(MiscUtils.ByteArraysEqual(receiveKey, encryptionMapping[i].ReceiveKey), "Encryption manager returned invalid key"); } // removing an encryption mapping that doesn't exist should return false { IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse("0:0:0:0:0:0:0:1"), 50000); assert(encryptionManager.RemoveEncryptionMapping(endpoint, time) == false, "Encryption manager removed invalid entry"); } // remove first and last encryption mappings assert(encryptionManager.RemoveEncryptionMapping(encryptionMapping[0].address, time), "Encryption manager failed to remove entry"); assert(encryptionManager.RemoveEncryptionMapping(encryptionMapping[encryptionMapping.Length - 1].address, time), "Encryption manager failed to remove entry"); // ensure removed encryption mappings cannot be looked up by address for (int i = 0; i < encryptionMapping.Length; i++) { int encryptionIndex = encryptionManager.FindEncryptionMapping(encryptionMapping[i].address, time); byte[] sendKey = encryptionManager.GetSendKey(encryptionIndex); byte[] receiveKey = encryptionManager.GetReceiveKey(encryptionIndex); if (i == 0 || i == (encryptionMapping.Length - 1)) { assert(sendKey == null, "Encryption manager returned invalid key"); assert(receiveKey == null, "Encryption manager returned invalid key"); } else { assert(sendKey != null, "Encryption manager failed to return key"); assert(receiveKey != null, "Encryption manager failed to return key"); assert(MiscUtils.ByteArraysEqual(sendKey, encryptionMapping[i].SendKey), "Encryption manager returned invalid key"); assert(MiscUtils.ByteArraysEqual(receiveKey, encryptionMapping[i].ReceiveKey), "Encryption manager returned invalid key"); } } // add the encryption mappings back in assert( encryptionManager.AddEncryptionMapping( encryptionMapping[0].address, encryptionMapping[0].SendKey, encryptionMapping[0].ReceiveKey, time, -1.0, encryptionMapping[0].TimeoutSeconds, encryptionMapping[0].ClientID), "Encryption manager failed to add mapping"); assert( encryptionManager.AddEncryptionMapping( encryptionMapping[encryptionMapping.Length - 1].address, encryptionMapping[encryptionMapping.Length - 1].SendKey, encryptionMapping[encryptionMapping.Length - 1].ReceiveKey, time, -1.0, encryptionMapping[encryptionMapping.Length - 1].TimeoutSeconds, encryptionMapping[encryptionMapping.Length - 1].ClientID), "Encryption manager failed to add mapping"); // ensure all encryption mappings can be looked up for (int i = 0; i < encryptionMapping.Length; i++) { int encryptionIndex = encryptionManager.FindEncryptionMapping(encryptionMapping[i].address, time); int timeoutSeconds = encryptionManager.GetTimeoutSeconds(encryptionIndex); uint clientID = encryptionManager.GetClientID(encryptionIndex); assert(timeoutSeconds == encryptionMapping[i].TimeoutSeconds, "Encryption manager returned invalid timeout seconds"); assert(clientID == encryptionMapping[i].ClientID, "Encryption manager returned invalid client ID"); byte[] sendKey = encryptionManager.GetSendKey(encryptionIndex); byte[] receiveKey = encryptionManager.GetReceiveKey(encryptionIndex); assert(sendKey != null, "Encryption manager failed to return key"); assert(receiveKey != null, "Encryption manager failed to return key"); assert(MiscUtils.ByteArraysEqual(sendKey, encryptionMapping[i].SendKey), "Encryption manager returned invalid key"); assert(MiscUtils.ByteArraysEqual(receiveKey, encryptionMapping[i].ReceiveKey), "Encryption manager returned invalid key"); } // check that encryption mappings time out properly time += Defines.NETCODE_TIMEOUT_SECONDS * 2; for (int i = 0; i < encryptionMapping.Length; i++) { int encryptionIndex = encryptionManager.FindEncryptionMapping(encryptionMapping[i].address, time); byte[] sendKey = encryptionManager.GetSendKey(encryptionIndex); byte[] receiveKey = encryptionManager.GetReceiveKey(encryptionIndex); assert(sendKey == null, "Encryption manager returned invalid key"); assert(receiveKey == null, "Encryption manager returned invalid key"); } // add the same encryption mappings after timeout for (int i = 0; i < encryptionMapping.Length; i++) { int encryptionIndex = encryptionManager.FindEncryptionMapping(encryptionMapping[i].address, time); assert(encryptionIndex == -1, "Encryption manager returned invalid index"); assert(encryptionManager.GetSendKey(encryptionIndex) == null, "Encryption manager returned invalid key"); assert(encryptionManager.GetReceiveKey(encryptionIndex) == null, "Encryption manager returned invalid key"); assert(encryptionManager.AddEncryptionMapping(encryptionMapping[i].address, encryptionMapping[i].SendKey, encryptionMapping[i].ReceiveKey, time, -1.0, encryptionMapping[i].TimeoutSeconds, encryptionMapping[i].ClientID), "Encryption manager failed to add mapping"); encryptionIndex = encryptionManager.FindEncryptionMapping(encryptionMapping[i].address, time); int timeoutSeconds = encryptionManager.GetTimeoutSeconds(encryptionIndex); uint clientID = encryptionManager.GetClientID(encryptionIndex); assert(timeoutSeconds == encryptionMapping[i].TimeoutSeconds, "Encryption manager returned invalid timeout seconds"); assert(clientID == encryptionMapping[i].ClientID, "Encryption manager returned invalid client ID"); byte[] sendKey = encryptionManager.GetSendKey(encryptionIndex); byte[] receiveKey = encryptionManager.GetReceiveKey(encryptionIndex); assert(sendKey != null, "Encryption manager failed to return key"); assert(receiveKey != null, "Encryption manager failed to return key"); assert(MiscUtils.ByteArraysEqual(sendKey, encryptionMapping[i].SendKey), "Encryption manager returned invalid key"); assert(MiscUtils.ByteArraysEqual(receiveKey, encryptionMapping[i].ReceiveKey), "Encryption manager returned invalid key"); } // reset the encryption manager and ensure all entries are removed encryptionManager.Reset(); for (int i = 0; i < encryptionMapping.Length; i++) { int encryptionIndex = encryptionManager.FindEncryptionMapping(encryptionMapping[i].address, time); byte[] sendKey = encryptionManager.GetSendKey(encryptionIndex); byte[] receiveKey = encryptionManager.GetReceiveKey(encryptionIndex); assert(sendKey == null, "Encryption manager returned invalid key"); assert(receiveKey == null, "Encryption manager returned invalid key"); } // test the expire time works as expected assert(encryptionManager.AddEncryptionMapping(encryptionMapping[0].address, encryptionMapping[0].SendKey, encryptionMapping[0].ReceiveKey, time, time + 1.0, encryptionMapping[0].TimeoutSeconds, encryptionMapping[0].ClientID), "Encryption manager failed to add mapping"); { int encryptionIndex = encryptionManager.FindEncryptionMapping(encryptionMapping[0].address, time); assert(encryptionIndex != -1, "Encryption manager failed to find entry"); assert(encryptionManager.FindEncryptionMapping(encryptionMapping[0].address, time + 1.1) == -1, "Encryption manager returned invalid entry"); encryptionManager.SetExpireTime(encryptionIndex, -1.0); assert(encryptionManager.FindEncryptionMapping(encryptionMapping[0].address, time) == encryptionIndex, "Encryption manager returned invalid entry"); } } public static void TestReplayProtection() { NetcodeReplayProtection replayProtection = new NetcodeReplayProtection(); for (int i = 0; i < 2; i++) { replayProtection.Reset(); assert(replayProtection.mostRecentSequence == 0, "Replay protection failed to reset"); // sequence numbers with high bits set should be ignored assert(replayProtection.AlreadyReceived(1UL << 63) == false, "Replay protection failed to ignore sequence"); assert(replayProtection.mostRecentSequence == 0, "Replay protection failed to ignore sequence"); const int max_sequence = (256 * 4); for (ulong sequence = 0; sequence < max_sequence; sequence++) { assert(replayProtection.AlreadyReceived(sequence) == false, "Replay protection found invalid sequence"); } // old packets outside the buffer should be considered already received assert(replayProtection.AlreadyReceived(0), "Replay protection failed to flag old sequence"); // packets received a second time should be flagged already received for (ulong sequence = max_sequence - 10; sequence < max_sequence; sequence++) { assert(replayProtection.AlreadyReceived(sequence), "Replay protection failed to flag already received sequence"); } // jumping ahead to a much higher sequence should be considered not already received assert(replayProtection.AlreadyReceived(max_sequence + 256) == false, "Replay protection flagged new sequence"); // old packets should be considered already received for (ulong sequence = 0; sequence < max_sequence; sequence++) { assert(replayProtection.AlreadyReceived(sequence), "Replay protection failed to flag old sequence"); } } } public static void TestClientServerConnection() { double time = 0.0; double dt = 1.0 / 10.0; NetworkSimulatorSocketManager socketMgr = new NetworkSimulatorSocketManager(); socketMgr.LatencyMS = 250; socketMgr.JitterMS = 250; socketMgr.PacketLossChance = 5; socketMgr.DuplicatePacketChance = 10; socketMgr.AutoTime = false; TokenFactory tokenFactory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); IPEndPoint serverEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT); IPEndPoint clientEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT + 100); Server server = new Server(socketMgr.CreateContext(serverEndpoint), 256, "127.0.0.1", TEST_SERVER_PORT, TEST_PROTOCOL_ID, _privateKey); server.Start(false); server.time = time; Client client = new Client((endpoint) => { var socket = socketMgr.CreateContext(clientEndpoint); socket.Bind(clientEndpoint); return socket; }); client.time = time; ulong clientID = 1000; byte[] userData = new byte[256]; KeyUtils.GenerateKey(userData); byte[] nonce = new byte[Defines.NETCODE_CONNECT_TOKEN_NONCE_BYTES]; KeyUtils.GenerateKey(nonce); byte[] connectToken = tokenFactory.GenerateConnectToken(new IPEndPoint[] { serverEndpoint }, TEST_CONNECT_TOKEN_EXPIRY, 5, nonce, clientID, userData); client.Connect(connectToken, false); while (true) { time += dt; client.Tick(time); server.Tick(time); socketMgr.Update(time); if (client.State <= ClientState.Disconnected) break; if (client.State == ClientState.Connected) break; } assert(client.State == ClientState.Connected, "Client failed to connect: " + client.State); int clientMessagesReceived = 0; int serverMessagesReceived = 0; server.OnClientMessageReceived += (sender, payload, size) => { // send message back to client sender.SendPayload(payload, size); serverMessagesReceived++; }; client.OnMessageReceived += (payload, size) => { clientMessagesReceived++; }; // send messages to the server until client and server have at least 10 messages each (or until we hit iteration max) for (int i = 0; i < 1000; i++) { time += dt; client.Tick(time); server.Tick(time); socketMgr.Update(time); byte[] testPayload = new byte[256]; client.Send(testPayload, testPayload.Length); if (clientMessagesReceived >= 10 && serverMessagesReceived >= 10) break; } assert(clientMessagesReceived >= 10 && serverMessagesReceived >= 10, "Sending messages failed!"); client.Disconnect(); server.Stop(); } public static void TestClientServerKeepAlive() { Console.WriteLine("TestClientServerKeepAlive"); double time = 0.0; double dt = 1.0 / 10.0; NetworkSimulatorSocketManager socketMgr = new NetworkSimulatorSocketManager(); socketMgr.LatencyMS = 250; socketMgr.JitterMS = 250; socketMgr.PacketLossChance = 5; socketMgr.DuplicatePacketChance = 10; socketMgr.AutoTime = false; TokenFactory tokenFactory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); IPEndPoint serverEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT); IPEndPoint clientEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT + 100); Server server = new Server(socketMgr.CreateContext(serverEndpoint), 256, "127.0.0.1", TEST_SERVER_PORT, TEST_PROTOCOL_ID, _privateKey); server.Start(false); server.time = time; Client client = new Client((endpoint) => { var socket = socketMgr.CreateContext(clientEndpoint); socket.Bind(clientEndpoint); return socket; }); client.time = time; ulong clientID = 1000; byte[] userData = new byte[256]; KeyUtils.GenerateKey(userData); byte[] nonce = new byte[Defines.NETCODE_CONNECT_TOKEN_NONCE_BYTES]; KeyUtils.GenerateKey(nonce); byte[] connectToken = tokenFactory.GenerateConnectToken(new IPEndPoint[] { serverEndpoint }, time, TEST_CONNECT_TOKEN_EXPIRY, 5, nonce, clientID, userData); client.Connect(connectToken, false); while (true) { time += dt; client.Tick(time); server.Tick(time); socketMgr.Update(time); if (client.State <= ClientState.Disconnected) break; if (client.State == ClientState.Connected) break; } assert(client.State == ClientState.Connected, "Client failed to connect: " + client.State); // pump client and server long enough that they would timeout without keep-alive packets int iterations = (int)Math.Ceiling(1.5 * Defines.NETCODE_TIMEOUT_SECONDS / dt); for (int i = 0; i < iterations; i++) { time += dt; client.Tick(time); server.Tick(time); socketMgr.Update(time); if (client.State <= ClientState.Disconnected) break; } assert(client.State == ClientState.Connected, "Client disconnected: " + client.State); client.Disconnect(); server.Stop(); } public static void TestClientServerMultipleClients() { Console.WriteLine("TestClientServerMultipleClients"); int[] client_counts = new int[] { 2, 32, 5 }; int start_stop_iters = client_counts.Length; double time = 0.0; double dt = 1.0 / 10.0; NetworkSimulatorSocketManager socketMgr = new NetworkSimulatorSocketManager(); socketMgr.LatencyMS = 250; socketMgr.JitterMS = 250; socketMgr.PacketLossChance = 5; socketMgr.DuplicatePacketChance = 10; socketMgr.AutoTime = false; TokenFactory tokenFactory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); IPEndPoint serverEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT); Server server = new Server(socketMgr.CreateContext(serverEndpoint), 32, "127.0.0.1", TEST_SERVER_PORT, TEST_PROTOCOL_ID, _privateKey); server.Start(false); server.time = time; ulong clientID = 1000UL; ulong tokenSequence = 0UL; for (int i = 0; i < start_stop_iters; i++) { Client[] clients = new Client[client_counts[i]]; for (int j = 0; j < clients.Length; j++) { IPEndPoint clientEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT + 100 + j); clients[j] = new Client((endpoint) => { var socket = socketMgr.CreateContext(clientEndpoint); socket.Bind(clientEndpoint); return socket; }); clients[j].time = time; byte[] nonce = new byte[Defines.NETCODE_CONNECT_TOKEN_NONCE_BYTES]; KeyUtils.GenerateKey(nonce); byte[] connectToken = tokenFactory.GenerateConnectToken(new IPEndPoint[] { serverEndpoint }, time, 30, 5, nonce, clientID, new byte[256]); clientID++; tokenSequence++; clients[j].Connect(connectToken, false); } var sw = new Stopwatch(); // make sure all clients can connect while (true) { time += dt; socketMgr.Update(time); server.Tick(time); foreach (Client c in clients) c.Tick(time); int numConnectedClients = 0; bool disconnect = false; foreach (Client c in clients) { if (c.State <= ClientState.Disconnected) { disconnect = true; break; } if (c.State == ClientState.Connected) numConnectedClients++; } if (disconnect) break; if (numConnectedClients == client_counts[i]) break; } foreach (Client c in clients) assert(c.State == ClientState.Connected, "Some clients failed to connect (iteration " + i + ")"); // make sure all clients can exchange packets with the server int[] clientPacketsReceived = new int[clients.Length]; int serverMessagesReceived = 0; server.OnClientMessageReceived += (sender, payload, size) => { // send message back to client sender.SendPayload(payload, size); serverMessagesReceived++; }; for (int x = 0; x < clients.Length; x++) { int clientIDX = x; clients[x].OnMessageReceived += (payload, size) => { clientPacketsReceived[clientIDX]++; }; } // send messages until everybody's got at least 10 messages (or we hit iteration max) for (int x = 0; x < 1000; x++) { time += dt; foreach (Client c in clients) c.Tick(time); server.Tick(time); socketMgr.Update(time); foreach (Client c in clients) { byte[] testPayload = new byte[256]; c.Send(testPayload, testPayload.Length); } if (clientPacketsReceived.All(n => n >= 10) && serverMessagesReceived >= 10) break; } assert(clientPacketsReceived.All(n => n >= 10) && serverMessagesReceived >= 10, "Packets failed to send"); foreach (Client c in clients) c.Disconnect(); } server.Stop(); } public static void TestClientServerMultipleServers() { Console.WriteLine("TestClientServerMultipleServers"); double time = 0.0; double dt = 1.0 / 10.0; NetworkSimulatorSocketManager socketMgr = new NetworkSimulatorSocketManager(); socketMgr.LatencyMS = 250; socketMgr.JitterMS = 250; socketMgr.PacketLossChance = 5; socketMgr.DuplicatePacketChance = 10; socketMgr.AutoTime = false; TokenFactory tokenFactory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); IPEndPoint serverEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT); IPEndPoint clientEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT + 100); Client client = new Client((endpoint) => { var socket = socketMgr.CreateContext(clientEndpoint); socket.Bind(clientEndpoint); return socket; }); client.time = time; Server server = new Server(socketMgr.CreateContext(serverEndpoint), 32, "127.0.0.1", TEST_SERVER_PORT, TEST_PROTOCOL_ID, _privateKey); server.Start(false); server.time = time; IPEndPoint[] testServerEndpoints = new IPEndPoint[] { new IPEndPoint(IPAddress.Parse("10.10.10.10"), 1000), new IPEndPoint(IPAddress.Parse("100.100.100.100"), 50000), new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT), }; byte[] nonce = new byte[Defines.NETCODE_CONNECT_TOKEN_NONCE_BYTES]; KeyUtils.GenerateKey(nonce); byte[] connectToken = tokenFactory.GenerateConnectToken(testServerEndpoints, time, 30, 5, nonce, 1000UL, new byte[256]); client.Connect(connectToken, false); while (true) { time += dt; socketMgr.Update(time); client.Tick(time); server.Tick(time); if (client.State <= ClientState.Disconnected) break; if (client.State == ClientState.Connected) break; } assert(client.State == ClientState.Connected, "Client failed to connect: " + client.State); int clientMessagesReceived = 0; int serverMessagesReceived = 0; server.OnClientMessageReceived += (sender, payload, size) => { // send message back to client sender.SendPayload(payload, size); serverMessagesReceived++; }; client.OnMessageReceived += (payload, size) => { clientMessagesReceived++; }; // send messages to the server until client and server have at least 10 messages each (or until we hit iteration max) for (int i = 0; i < 1000; i++) { time += dt; client.Tick(time); server.Tick(time); socketMgr.Update(time); byte[] testPayload = new byte[256]; client.Send(testPayload, testPayload.Length); if (clientMessagesReceived >= 10 && serverMessagesReceived >= 10) break; } assert(clientMessagesReceived >= 10 && serverMessagesReceived >= 10, "Sending messages failed!"); client.Disconnect(); server.Stop(); } public static void TestConnectTokenExpired() { Console.WriteLine("TestConnectTokenExpired"); double time = 0.0; NetworkSimulatorSocketManager socketMgr = new NetworkSimulatorSocketManager(); socketMgr.LatencyMS = 250; socketMgr.JitterMS = 250; socketMgr.PacketLossChance = 5; socketMgr.DuplicatePacketChance = 10; socketMgr.AutoTime = false; TokenFactory tokenFactory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); IPEndPoint clientEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT + 100); Client client = new Client((endpoint) => { var socket = socketMgr.CreateContext(clientEndpoint); socket.Bind(clientEndpoint); return socket; }); client.time = time; IPEndPoint[] testServerEndpoints = new IPEndPoint[] { new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT), }; byte[] nonce = new byte[Defines.NETCODE_CONNECT_TOKEN_NONCE_BYTES]; KeyUtils.GenerateKey(nonce); // token expires one second from when it is created byte[] connectToken = tokenFactory.GenerateConnectToken(testServerEndpoints, time, 1, 1, nonce, 1000UL, new byte[256]); client.Connect(connectToken, false); // advancing time by 10 seconds should immediately force token to expire time += 10.0; client.Tick(time); assert(client.State == ClientState.ConnectTokenExpired, "Connect token failed to expire!"); } public static void TestClientInvalidConnectToken() { Console.WriteLine("TestClientInvalidConnectToken"); double time = 0.0; NetworkSimulatorSocketManager socketMgr = new NetworkSimulatorSocketManager(); socketMgr.LatencyMS = 250; socketMgr.JitterMS = 250; socketMgr.PacketLossChance = 5; socketMgr.DuplicatePacketChance = 10; socketMgr.AutoTime = false; TokenFactory tokenFactory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); IPEndPoint clientEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT + 100); Client client = new Client((endpoint) => { var socket = socketMgr.CreateContext(clientEndpoint); socket.Bind(clientEndpoint); return socket; }); client.time = time; IPEndPoint[] testServerEndpoints = new IPEndPoint[] { new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT), }; // fill a connect token with random bytes // this should cause the client to immediately fail with InvalidConnectToken byte[] connectToken = new byte[2048]; KeyUtils.GenerateKey(connectToken); client.Connect(connectToken, false); assert(client.State == ClientState.InvalidConnectToken, "Client accepted invalid connect token"); } public static void TestConnectionTimeout() { Console.WriteLine("TestConnectionTimeout"); double time = 0.0; double dt = 1.0 / 10.0; NetworkSimulatorSocketManager socketMgr = new NetworkSimulatorSocketManager(); socketMgr.LatencyMS = 250; socketMgr.JitterMS = 250; socketMgr.PacketLossChance = 5; socketMgr.DuplicatePacketChance = 10; socketMgr.AutoTime = false; TokenFactory tokenFactory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); IPEndPoint serverEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT); IPEndPoint clientEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT + 100); Server server = new Server(socketMgr.CreateContext(serverEndpoint), 256, "127.0.0.1", TEST_SERVER_PORT, TEST_PROTOCOL_ID, _privateKey); server.Start(false); server.time = time; Client client = new Client((endpoint) => { var socket = socketMgr.CreateContext(clientEndpoint); socket.Bind(clientEndpoint); return socket; }); client.time = time; ulong clientID = 1000; byte[] userData = new byte[256]; KeyUtils.GenerateKey(userData); byte[] nonce = new byte[Defines.NETCODE_CONNECT_TOKEN_NONCE_BYTES]; KeyUtils.GenerateKey(nonce); byte[] connectToken = tokenFactory.GenerateConnectToken(new IPEndPoint[] { serverEndpoint }, TEST_CONNECT_TOKEN_EXPIRY, 5, nonce, clientID, userData); client.Connect(connectToken, false); while (true) { time += dt; client.Tick(time); server.Tick(time); socketMgr.Update(time); if (client.State <= ClientState.Disconnected) break; if (client.State == ClientState.Connected) break; } assert(client.State == ClientState.Connected, "Client failed to connect: " + client.State); // now stop updating the server and run long enough that the client should disconnect int iterations = (int)Math.Ceiling(1.5 * Defines.NETCODE_TIMEOUT_SECONDS / dt); for (int i = 0; i < iterations; i++) { time += dt; socketMgr.Update(time); client.Tick(time); if (client.State <= ClientState.Disconnected) break; } assert(client.State == ClientState.ConnectionTimedOut, "Client failed to time out: " + client.State); client.Disconnect(); server.Stop(); } public static void TestChallengeResponseTimeout() { Console.WriteLine("TestChallengeResponseTimeout"); double time = 0.0; double dt = 1.0 / 10.0; NetworkSimulatorSocketManager socketMgr = new NetworkSimulatorSocketManager(); socketMgr.LatencyMS = 250; socketMgr.JitterMS = 250; socketMgr.PacketLossChance = 5; socketMgr.DuplicatePacketChance = 10; socketMgr.AutoTime = false; TokenFactory tokenFactory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); IPEndPoint serverEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT); IPEndPoint clientEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT + 100); Server server = new Server(socketMgr.CreateContext(serverEndpoint), 256, "127.0.0.1", TEST_SERVER_PORT, TEST_PROTOCOL_ID, _privateKey); server.Start(false); server.time = time; // server should not respond to challenge response packets. this should make the client time out with ChallengeResponseTimedOut state server.debugIgnoreChallengeResponse = true; Client client = new Client((endpoint) => { var socket = socketMgr.CreateContext(clientEndpoint); socket.Bind(clientEndpoint); return socket; }); client.time = time; ulong clientID = 1000; byte[] userData = new byte[256]; KeyUtils.GenerateKey(userData); byte[] nonce = new byte[Defines.NETCODE_CONNECT_TOKEN_NONCE_BYTES]; KeyUtils.GenerateKey(nonce); byte[] connectToken = tokenFactory.GenerateConnectToken(new IPEndPoint[] { serverEndpoint }, TEST_CONNECT_TOKEN_EXPIRY, 5, nonce, clientID, userData); client.Connect(connectToken, false); while (true) { time += dt; client.Tick(time); server.Tick(time); socketMgr.Update(time); if (client.State <= ClientState.Disconnected) break; if (client.State == ClientState.Connected) break; } assert(client.State == ClientState.ChallengeResponseTimedOut, "Wrong client state (should be ChallengeResponseTimedOut): " + client.State); client.Disconnect(); server.Stop(); } public static void TestConnectionRequestTimeout() { Console.WriteLine("TestConnectionRequestTimeout"); double time = 0.0; double dt = 1.0 / 10.0; NetworkSimulatorSocketManager socketMgr = new NetworkSimulatorSocketManager(); socketMgr.LatencyMS = 250; socketMgr.JitterMS = 250; socketMgr.PacketLossChance = 5; socketMgr.DuplicatePacketChance = 10; socketMgr.AutoTime = false; TokenFactory tokenFactory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); IPEndPoint serverEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT); IPEndPoint clientEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT + 100); Server server = new Server(socketMgr.CreateContext(serverEndpoint), 256, "127.0.0.1", TEST_SERVER_PORT, TEST_PROTOCOL_ID, _privateKey); server.Start(false); server.time = time; // server should not respond to connection request packets. this should make the client time out with ConnectionRequestTimedOut state server.debugIgnoreConnectionRequest = true; Client client = new Client((endpoint) => { var socket = socketMgr.CreateContext(clientEndpoint); socket.Bind(clientEndpoint); return socket; }); client.time = time; ulong clientID = 1000; byte[] userData = new byte[256]; KeyUtils.GenerateKey(userData); byte[] nonce = new byte[Defines.NETCODE_CONNECT_TOKEN_NONCE_BYTES]; KeyUtils.GenerateKey(nonce); byte[] connectToken = tokenFactory.GenerateConnectToken(new IPEndPoint[] { serverEndpoint }, TEST_CONNECT_TOKEN_EXPIRY, 5, nonce, clientID, userData); client.Connect(connectToken, false); while (true) { time += dt; client.Tick(time); server.Tick(time); socketMgr.Update(time); if (client.State <= ClientState.Disconnected) break; if (client.State == ClientState.Connected) break; } assert(client.State == ClientState.ConnectionRequestTimedOut, "Wrong client state (should be ConnectionRequestTimedOut): " + client.State); client.Disconnect(); server.Stop(); } public static void TestConnectionDenied() { Console.WriteLine("TestConnectionDenied"); double time = 0.0; double dt = 1.0 / 10.0; NetworkSimulatorSocketManager socketMgr = new NetworkSimulatorSocketManager(); socketMgr.LatencyMS = 250; socketMgr.JitterMS = 250; socketMgr.PacketLossChance = 5; socketMgr.DuplicatePacketChance = 10; socketMgr.AutoTime = false; TokenFactory tokenFactory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); IPEndPoint serverEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT); IPEndPoint clientEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT + 100); IPEndPoint clientEndpoint2 = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT + 101); // server only has room for one player - second player should be denied Server server = new Server(socketMgr.CreateContext(serverEndpoint), 1, "127.0.0.1", TEST_SERVER_PORT, TEST_PROTOCOL_ID, _privateKey); server.Start(false); server.time = time; Client client = new Client((endpoint) => { var socket = socketMgr.CreateContext(clientEndpoint); socket.Bind(clientEndpoint); return socket; }); client.time = time; ulong clientID = 1000; byte[] userData = new byte[256]; KeyUtils.GenerateKey(userData); byte[] nonce = new byte[Defines.NETCODE_CONNECT_TOKEN_NONCE_BYTES]; KeyUtils.GenerateKey(nonce); byte[] connectToken = tokenFactory.GenerateConnectToken(new IPEndPoint[] { serverEndpoint }, TEST_CONNECT_TOKEN_EXPIRY, 5, nonce, clientID, userData); client.Connect(connectToken, false); while (true) { time += dt; client.Tick(time); server.Tick(time); socketMgr.Update(time); if (client.State <= ClientState.Disconnected) break; if (client.State == ClientState.Connected) break; } assert(client.State == ClientState.Connected, "Client failed to connect: " + client.State); // now attempt to connect a second client. it should be denied. Client client2 = new Client((endpoint) => { var socket = socketMgr.CreateContext(clientEndpoint2); socket.Bind(clientEndpoint2); return socket; }); client2.time = time; ulong clientID2 = 1001; byte[] connectToken2 = tokenFactory.GenerateConnectToken(new IPEndPoint[] { serverEndpoint }, TEST_CONNECT_TOKEN_EXPIRY, 5, nonce, clientID2, userData); client2.Connect(connectToken2, false); while (true) { time += dt; client.Tick(time); client2.Tick(time); server.Tick(time); socketMgr.Update(time); if (client2.State <= ClientState.Disconnected) break; if (client2.State == ClientState.Connected) break; } assert(client.State == ClientState.Connected, "Client1 was disconnected: " + client.State); assert(client2.State == ClientState.ConnectionDenied, "Client2 connection not denied: " + client2.State); client.Disconnect(); client2.Disconnect(); server.Stop(); } public static void TestClientSideDisconnect() { Console.WriteLine("TestClientSideDisconnect"); double time = 0.0; double dt = 1.0 / 10.0; NetworkSimulatorSocketManager socketMgr = new NetworkSimulatorSocketManager(); socketMgr.LatencyMS = 250; socketMgr.JitterMS = 250; socketMgr.PacketLossChance = 5; socketMgr.DuplicatePacketChance = 10; socketMgr.AutoTime = false; TokenFactory tokenFactory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); IPEndPoint serverEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT); IPEndPoint clientEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT + 100); // server only has room for one player - second player should be denied Server server = new Server(socketMgr.CreateContext(serverEndpoint), 1, "127.0.0.1", TEST_SERVER_PORT, TEST_PROTOCOL_ID, _privateKey); server.Start(false); server.time = time; Client client = new Client((endpoint) => { var socket = socketMgr.CreateContext(clientEndpoint); socket.Bind(clientEndpoint); return socket; }); client.time = time; ulong clientID = 1000; byte[] userData = new byte[256]; KeyUtils.GenerateKey(userData); byte[] nonce = new byte[Defines.NETCODE_CONNECT_TOKEN_NONCE_BYTES]; KeyUtils.GenerateKey(nonce); byte[] connectToken = tokenFactory.GenerateConnectToken(new IPEndPoint[] { serverEndpoint }, TEST_CONNECT_TOKEN_EXPIRY, 5, nonce, clientID, userData); client.Connect(connectToken, false); while (true) { time += dt; client.Tick(time); server.Tick(time); socketMgr.Update(time); if (client.State <= ClientState.Disconnected) break; if (client.State == ClientState.Connected) break; } assert(client.State == ClientState.Connected, "Client failed to connect: " + client.State); client.Disconnect(); bool clientDisconnected = false; server.OnClientDisconnected += (remoteClient) => { clientDisconnected = true; }; time += 1.0; socketMgr.Update(time); server.Tick(time); assert(clientDisconnected, "Server did not detect client disconnect"); server.Stop(); } public static void TestServerSideDisconnect() { Console.WriteLine("TestServerSideDisconnect"); double time = 0.0; double dt = 1.0 / 10.0; NetworkSimulatorSocketManager socketMgr = new NetworkSimulatorSocketManager(); socketMgr.LatencyMS = 250; socketMgr.JitterMS = 250; socketMgr.PacketLossChance = 5; socketMgr.DuplicatePacketChance = 10; socketMgr.AutoTime = false; TokenFactory tokenFactory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); IPEndPoint serverEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT); IPEndPoint clientEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT + 100); // server only has room for one player - second player should be denied Server server = new Server(socketMgr.CreateContext(serverEndpoint), 1, "127.0.0.1", TEST_SERVER_PORT, TEST_PROTOCOL_ID, _privateKey); server.Start(false); server.time = time; Client client = new Client((endpoint) => { var socket = socketMgr.CreateContext(clientEndpoint); socket.Bind(clientEndpoint); return socket; }); client.time = time; ulong clientID = 1000; byte[] userData = new byte[256]; KeyUtils.GenerateKey(userData); byte[] nonce = new byte[Defines.NETCODE_CONNECT_TOKEN_NONCE_BYTES]; KeyUtils.GenerateKey(nonce); byte[] connectToken = tokenFactory.GenerateConnectToken(new IPEndPoint[] { serverEndpoint }, TEST_CONNECT_TOKEN_EXPIRY, 5, nonce, clientID, userData); client.Connect(connectToken, false); RemoteClient remoteClient = null; server.OnClientConnected += (newClient) => { remoteClient = newClient; }; while (true) { time += dt; client.Tick(time); server.Tick(time); socketMgr.Update(time); if (client.State <= ClientState.Disconnected) break; if (server.NumConnectedClients == 1) break; } assert(client.State == ClientState.Connected, "Client failed to connect: " + client.State); assert(remoteClient != null, "Server failed to fire OnClientConnected"); server.Disconnect(remoteClient); time += 1.0; socketMgr.Update(time); server.Tick(time); client.Tick(time); assert(client.State == ClientState.Disconnected, "Client was not disconnected: " + client.State); server.Stop(); } public static void TestReconnect() { Console.WriteLine("TestReconnect"); double time = 0.0; double dt = 1.0 / 10.0; NetworkSimulatorSocketManager socketMgr = new NetworkSimulatorSocketManager(); socketMgr.LatencyMS = 250; socketMgr.JitterMS = 250; socketMgr.PacketLossChance = 5; socketMgr.DuplicatePacketChance = 10; socketMgr.AutoTime = false; TokenFactory tokenFactory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); IPEndPoint serverEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT); IPEndPoint clientEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT + 100); // server only has room for one player - second player should be denied Server server = new Server(socketMgr.CreateContext(serverEndpoint), 1, "127.0.0.1", TEST_SERVER_PORT, TEST_PROTOCOL_ID, _privateKey); server.Start(false); server.time = time; Client client = new Client((endpoint) => { var socket = socketMgr.CreateContext(clientEndpoint); socket.Bind(clientEndpoint); return socket; }); client.time = time; ulong clientID = 1000; byte[] userData = new byte[256]; KeyUtils.GenerateKey(userData); byte[] nonce = new byte[Defines.NETCODE_CONNECT_TOKEN_NONCE_BYTES]; KeyUtils.GenerateKey(nonce); byte[] connectToken = tokenFactory.GenerateConnectToken(new IPEndPoint[] { serverEndpoint }, TEST_CONNECT_TOKEN_EXPIRY, 5, nonce, clientID, userData); client.Connect(connectToken, false); RemoteClient remoteClient = null; server.OnClientConnected += (newClient) => { remoteClient = newClient; }; while (true) { time += dt; client.Tick(time); server.Tick(time); socketMgr.Update(time); if (client.State <= ClientState.Disconnected) break; if (server.NumConnectedClients == 1) break; } assert(client.State == ClientState.Connected, "Client failed to connect: " + client.State); assert(remoteClient != null, "Server failed to fire OnClientConnected"); server.Disconnect(remoteClient); time += 1.0; socketMgr.Update(time); server.Tick(time); client.Tick(time); assert(client.State == ClientState.Disconnected, "Client was not disconnected: " + client.State); // now get a new connect token and attempt to reconnect connectToken = tokenFactory.GenerateConnectToken(new IPEndPoint[] { serverEndpoint }, TEST_CONNECT_TOKEN_EXPIRY, 5, nonce, clientID + 1, userData); client.Connect(connectToken, false); while (true) { time += dt; client.Tick(time); server.Tick(time); socketMgr.Update(time); if (client.State <= ClientState.Disconnected) break; if (client.State == ClientState.Connected) break; } assert(client.State == ClientState.Connected, "Client failed to reconnect: " + client.State); client.Disconnect(); server.Stop(); } public static void SoakTestClientServerConnection(int testDurationMinutes = -1) { double time = 0.0; double dt = 1.0 / 10.0; NetworkSimulatorSocketManager socketMgr = new NetworkSimulatorSocketManager(); socketMgr.LatencyMS = 250; socketMgr.JitterMS = 250; socketMgr.PacketLossChance = 5; socketMgr.DuplicatePacketChance = 10; socketMgr.AutoTime = false; TokenFactory tokenFactory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); ulong nextClientID = 0; const int NUM_SERVERS = 32; const int NUM_CLIENTS = 1024; const int BASE_SERVER_PORT = 40000; const int BASE_CLIENT_PORT = 1000; Client[] clients = new Client[NUM_CLIENTS]; Server[] servers = new Server[NUM_SERVERS]; IPEndPoint[] serverEndpoints = new IPEndPoint[NUM_SERVERS]; Dictionary receivedPackets = new Dictionary(); Random rand = new Random(); // create and start servers for (int i = 0; i < servers.Length; i++) { IPEndPoint serverEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), BASE_SERVER_PORT + i); serverEndpoints[i] = serverEndpoint; var socket = socketMgr.CreateContext(serverEndpoint); int slots = rand.Next(0, NUM_CLIENTS) + 1; var server = new Server(socket, slots, "127.0.0.1", BASE_SERVER_PORT + i, TEST_PROTOCOL_ID, _privateKey); server.Start(false); server.time = time; server.OnClientMessageReceived += (sender, payload, size) => { // send payload back to client sender.SendPayload(payload, size); }; servers[i] = server; } Dictionary clientsConnected = new Dictionary(); // create clients for (int i = 0; i < clients.Length; i++) { IPEndPoint clientEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), BASE_CLIENT_PORT + i); Client client = new Client((endpoint) => { var socket = socketMgr.CreateContext(clientEndpoint); socket.Bind(clientEndpoint); return socket; }); // keep track of how many packets the client has received from the server client.OnMessageReceived += (payload, size) => { receivedPackets[client]++; }; // if the client disconnects from a server, reset packets received client.OnStateChanged += (state) => { if (state == ClientState.Disconnected) receivedPackets[client] = 0; else if (state == ClientState.Connected) clientsConnected[client] = true; }; clientsConnected.Add(client, false); receivedPackets.Add(client, 0); client.time = time; clients[i] = client; } bool runTest = true; Stopwatch sw = new Stopwatch(); sw.Start(); // now keep running the test! while (runTest) { time += dt; socketMgr.Update(time); foreach (Server server in servers) server.Tick(time); foreach (Client client in clients) client.Tick(time); // iterate over each client: // - if state is disconnected, grab a new connect token and connect // - if state is connected: if packets received is >=10, disconnect. otherwise, send a packet byte[] nonce = new byte[Defines.NETCODE_CONNECT_TOKEN_NONCE_BYTES]; KeyUtils.GenerateKey(nonce); foreach (Client client in clients) { if (client.State == ClientState.Disconnected) { var connectToken = tokenFactory.GenerateConnectToken(serverEndpoints, time, serverEndpoints.Length * 5, 5, nonce, nextClientID++, new byte[256]); client.Connect(connectToken, false); } else if (client.State == ClientState.Connected) { if (receivedPackets[client] >= 10) client.Disconnect(); else { byte[] payload = new byte[100]; client.Send(payload, 100); } } else if (client.State < ClientState.Disconnected) break; } if (testDurationMinutes > 0) { runTest = sw.ElapsedMilliseconds <= (1000 * 60 * testDurationMinutes); } } foreach (Client client in clients) { assert(client.State >= ClientState.Disconnected, "Client in bad state: " + client.State); assert(clientsConnected[client] == true, "Client never connected!"); } foreach (Server server in servers) server.Stop(); } public static void TestConnectionRequestPacket() { // generate a connect token TokenFactory factory = new TokenFactory(TEST_PROTOCOL_ID, _privateKey); byte[] userData = new byte[256]; KeyUtils.GenerateKey(userData); byte[] nonce = new byte[Defines.NETCODE_CONNECT_TOKEN_NONCE_BYTES]; KeyUtils.GenerateKey(nonce); byte[] publicConnectTokenBuffer = factory.GenerateConnectToken(new IPEndPoint[] { new IPEndPoint( IPAddress.Parse("127.0.0.1"), TEST_SERVER_PORT ) }, TEST_CONNECT_TOKEN_EXPIRY, 5, nonce, TEST_CLIENT_ID, userData); NetcodePublicConnectToken publicConnectToken = new NetcodePublicConnectToken(); using (var reader = ByteArrayReaderWriter.Get(publicConnectTokenBuffer)) publicConnectToken.Read(reader); // set up a connection request packet wrapping the public token byte[] packetBuffer = new byte[1 + 13 + 8 + 8 + 8 + Defines.NETCODE_CONNECT_TOKEN_PRIVATE_BYTES]; using (var writer = ByteArrayReaderWriter.Get(packetBuffer)) { writer.Write((byte)0); writer.WriteASCII(Defines.NETCODE_VERSION_INFO_STR); writer.Write(publicConnectToken.ProtocolID); writer.Write(publicConnectToken.ExpireTimestamp); writer.Write(publicConnectToken.ConnectTokenNonce); writer.Write(publicConnectToken.PrivateConnectTokenBytes); } // read connection request back in var connectionRequestPacket = new NetcodeConnectionRequestPacket(); using (var reader = ByteArrayReaderWriter.Get(packetBuffer)) { byte prefixByte = reader.ReadByte(); assert(prefixByte == 0, "Wrong prefix byte"); assert(connectionRequestPacket.Read(reader, packetBuffer.Length - (int)reader.ReadPosition, TEST_PROTOCOL_ID), "Failed to read connection request packet"); } // ensure read packet matches what was written assert(connectionRequestPacket.Expiration == publicConnectToken.ExpireTimestamp, "Expiration timestamps do not match"); //assert(connectionRequestPacket.TokenSequenceNum == publicConnectToken.ConnectTokenSequence, "Token sequence numbers do not match"); assert(MiscUtils.ByteArraysEqual(connectionRequestPacket.ConnectTokenBytes, publicConnectToken.PrivateConnectTokenBytes), "Token private bytes do not match"); } public static void TestConnectionDeniedPacket() { ulong testSequence = 1000; byte[] key = new byte[32]; KeyUtils.GenerateKey(key); byte[] packet = serializePacket(testSequence, new NetcodePacketHeader() { PacketType = NetcodePacketType.ConnectionDenied }, (writer) => { }, key); // read packet back in NetcodePacketHeader header = new NetcodePacketHeader(); using (var reader = ByteArrayReaderWriter.Get(packet)) { header.Read(reader); assert(header.PacketType == NetcodePacketType.ConnectionDenied, "Packet type mismatch"); assert(header.SequenceNumber == testSequence, "Packet sequence number mismatch"); } } public static void TestConnectionKeepAlivePacket() { ulong testSequence = 1000; uint testMaxSlots = 256; uint testClientIndex = 10; byte[] key = new byte[32]; KeyUtils.GenerateKey(key); byte[] packet = serializePacket(testSequence, new NetcodePacketHeader() { PacketType = NetcodePacketType.ConnectionKeepAlive }, (writer) => { var keepAlivePacket = new NetcodeKeepAlivePacket(); keepAlivePacket.ClientIndex = testClientIndex; keepAlivePacket.MaxSlots = testMaxSlots; keepAlivePacket.Write(writer); }, key); // read packet back in NetcodePacketHeader header = new NetcodePacketHeader(); using (var reader = ByteArrayReaderWriter.Get(packet)) { header.Read(reader); assert(header.PacketType == NetcodePacketType.ConnectionKeepAlive, "Packet type mismatch"); assert(header.SequenceNumber == testSequence, "Packet sequence number mismatch"); NetcodeKeepAlivePacket keepAlivePacket = new NetcodeKeepAlivePacket() { Header = header }; assert(keepAlivePacket.Read(reader, packet.Length - (int)reader.ReadPosition, key, TEST_PROTOCOL_ID), "Failed to read packet"); assert(keepAlivePacket.ClientIndex == testClientIndex, "Client index mismatch"); assert(keepAlivePacket.MaxSlots == testMaxSlots, "Max slot count mismatch"); } } public static void TestConnectionChallengePacket() { NetcodeChallengeToken challengeToken = new NetcodeChallengeToken(); challengeToken.ClientID = TEST_CLIENT_ID; byte[] userData = new byte[256]; KeyUtils.GenerateKey(userData); challengeToken.UserData = userData; byte[] challengeKey = new byte[32]; KeyUtils.GenerateKey(challengeKey); byte[] key = new byte[32]; KeyUtils.GenerateKey(key); ulong testSequence = 500; ulong testChallengeSequence = 1000; byte[] tokenBytes = new byte[300]; using (var writer = ByteArrayReaderWriter.Get(tokenBytes)) challengeToken.Write(writer); byte[] encryptedToken = BufferPool.GetBuffer(300); int encryptedTokenBytes = PacketIO.EncryptChallengeToken(testChallengeSequence, tokenBytes, challengeKey, encryptedToken); var challengePacket = new NetcodeConnectionChallengeResponsePacket(); challengePacket.ChallengeTokenSequence = testChallengeSequence; challengePacket.ChallengeTokenBytes = encryptedToken; byte[] packet = serializePacket(testSequence, new NetcodePacketHeader() { PacketType = NetcodePacketType.ConnectionChallenge }, (writer) => { challengePacket.Write(writer); }, key); // read packet back in NetcodePacketHeader header = new NetcodePacketHeader(); using (var reader = ByteArrayReaderWriter.Get(packet)) { header.Read(reader); assert(header.PacketType == NetcodePacketType.ConnectionChallenge, "Packet type mismatch"); assert(header.SequenceNumber == testSequence, "Packet sequence number mismatch"); NetcodeConnectionChallengeResponsePacket packetReader = new NetcodeConnectionChallengeResponsePacket() { Header = header }; assert(packetReader.Read(reader, packet.Length - (int)reader.ReadPosition, key, TEST_PROTOCOL_ID), "Failed to read packet"); assert(packetReader.ChallengeTokenSequence == testChallengeSequence, "Challenge sequence mismatch"); assert(MiscUtils.ByteArraysEqual(packetReader.ChallengeTokenBytes, encryptedToken), "Challenge token encrypted bytes mismatch"); NetcodeChallengeToken readToken = new NetcodeChallengeToken(); assert(readToken.Read(packetReader.ChallengeTokenBytes, packetReader.ChallengeTokenSequence, challengeKey), "Failed to decrypt/read challenge token"); assert(readToken.ClientID == TEST_CLIENT_ID, "Client ID mismatch"); assert(MiscUtils.ByteArraysEqual(readToken.UserData, userData), "User data mismatch"); } } public static void TestConnectionPayloadPacket() { ulong testSequence = 1000; byte[] key = new byte[32]; KeyUtils.GenerateKey(key); byte[] payload = new byte[1024]; KeyUtils.GenerateKey(payload); byte[] packet = serializePacket(testSequence, new NetcodePacketHeader() { PacketType = NetcodePacketType.ConnectionPayload }, (writer) => { writer.Write(payload); }, key); // read packet back in NetcodePacketHeader header = new NetcodePacketHeader(); using (var reader = ByteArrayReaderWriter.Get(packet)) { header.Read(reader); assert(header.PacketType == NetcodePacketType.ConnectionPayload, "Packet type mismatch"); assert(header.SequenceNumber == testSequence, "Packet sequence number mismatch"); NetcodePayloadPacket payloadPacket = new NetcodePayloadPacket() { Header = header }; assert(payloadPacket.Read(reader, packet.Length - (int)reader.ReadPosition, key, TEST_PROTOCOL_ID), "Failed to read packet"); byte[] readPayload = new byte[payloadPacket.Length]; Buffer.BlockCopy(payloadPacket.Payload, 0, readPayload, 0, payloadPacket.Length); assert(MiscUtils.ByteArraysEqual(readPayload, payload), "Payload mismatch"); } } public static void TestConnectionDisconnectPacket() { ulong testSequence = 1000; byte[] key = new byte[32]; KeyUtils.GenerateKey(key); byte[] packet = serializePacket(testSequence, new NetcodePacketHeader() { PacketType = NetcodePacketType.ConnectionDisconnect }, (writer) => { }, key); // read packet back in NetcodePacketHeader header = new NetcodePacketHeader(); using (var reader = ByteArrayReaderWriter.Get(packet)) { header.Read(reader); assert(header.PacketType == NetcodePacketType.ConnectionDisconnect, "Packet type mismatch"); assert(header.SequenceNumber == testSequence, "Packet sequence number mismatch"); } } private static byte[] serializePacket(ulong sequence, NetcodePacketHeader packetHeader, Action write, byte[] key) { byte[] tempPacket = new byte[2048]; int writeLen = 0; using (var writer = ByteArrayReaderWriter.Get(tempPacket)) { write(writer); writeLen = (int)writer.WritePosition; } return serializePacketToBuffer(sequence, packetHeader, tempPacket, writeLen, key); } private static byte[] serializePacketToBuffer(ulong sequence, NetcodePacketHeader packetHeader, byte[] packetData, int packetDataLen, byte[] key) { // assign a sequence number to this packet packetHeader.SequenceNumber = sequence; // encrypt packet data byte[] encryptedPacketBuffer = new byte[2048]; int encryptedBytes = PacketIO.EncryptPacketData(packetHeader, TEST_PROTOCOL_ID, packetData, packetDataLen, key, encryptedPacketBuffer); int packetLen = 0; // write packet to byte array var packetBuffer = new byte[2048]; using (var packetWriter = ByteArrayReaderWriter.Get(packetBuffer)) { packetHeader.Write(packetWriter); packetWriter.WriteBuffer(encryptedPacketBuffer, encryptedBytes); packetLen = (int)packetWriter.WritePosition; } byte[] ret = new byte[packetLen]; Buffer.BlockCopy(packetBuffer, 0, ret, 0, packetLen); return ret; } private static void assert(bool condition, string message = null) { if (!condition) throw new Exception(message); } } }