using System.Buffers.Binary; using System.Net; using AcDream.Core.Net; using AcDream.Core.Net.Packets; namespace AcDream.Core.Net.Tests; /// /// Live integration test that talks to a real ACE server. Skipped by /// default so CI doesn't fail for developers without a running server. /// To run: /// /// set ACDREAM_LIVE=1 /// set ACDREAM_TEST_USER=testaccount /// set ACDREAM_TEST_PASS=testpassword /// set ACDREAM_TEST_HOST=127.0.0.1 (optional, default) /// set ACDREAM_TEST_PORT=9000 (optional, default) /// dotnet test --filter LiveHandshake /// /// /// /// Credential handling: the test reads the username and password /// from environment variables and uses them in one outbound LoginRequest /// packet. They are never written to disk, never logged to console, never /// included in assertion messages, and never committed. When the test /// prints diagnostics they are reduced to their length so mistakes in /// the env-var setup are distinguishable from server errors. /// /// public class LiveHandshakeTests { [Fact] public void Live_LoginRequest_ReceivesConnectRequestFromServer() { if (Environment.GetEnvironmentVariable("ACDREAM_LIVE") != "1") return; // skipped — not a failure var host = Environment.GetEnvironmentVariable("ACDREAM_TEST_HOST") ?? "127.0.0.1"; var portStr = Environment.GetEnvironmentVariable("ACDREAM_TEST_PORT") ?? "9000"; var user = Environment.GetEnvironmentVariable("ACDREAM_TEST_USER"); var pass = Environment.GetEnvironmentVariable("ACDREAM_TEST_PASS"); Assert.NotNull(user); Assert.NotNull(pass); Assert.NotEmpty(user!); Assert.NotEmpty(pass!); var remote = new IPEndPoint(IPAddress.Parse(host), int.Parse(portStr)); using var net = new NetClient(remote); // Build and send the LoginRequest datagram. Header has only the // LoginRequest flag; checksum is unencrypted (the ISAAC keystream // is established only *after* the server sends us ConnectRequest). uint timestamp = (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); byte[] loginBody = LoginRequest.Build(user, pass, timestamp); var loginHeader = new PacketHeader { Flags = PacketHeaderFlags.LoginRequest }; byte[] loginDatagram = PacketCodec.Encode(loginHeader, loginBody, outboundIsaac: null); Console.WriteLine($"[live] sending {loginDatagram.Length}-byte LoginRequest to {remote} " + $"(user.len={user.Length}, pass.len={pass.Length})"); net.Send(loginDatagram); // Expect at least one packet back within 5 seconds. ACE can chunk // the handshake across multiple datagrams so we loop until we find // a ConnectRequest or hit the overall deadline. var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(5); Packet? connectRequest = null; int packetsReceived = 0; while (DateTime.UtcNow < deadline && connectRequest is null) { var bytes = net.Receive(deadline - DateTime.UtcNow, out var from); if (bytes is null) break; packetsReceived++; Console.WriteLine($"[live] received {bytes.Length}-byte datagram from {from}"); var decoded = PacketCodec.TryDecode(bytes, inboundIsaac: null); Console.WriteLine($"[live] decode result: {decoded.Error}, " + $"flags: {(decoded.Packet?.Header.Flags.ToString() ?? "n/a")}"); if (decoded.IsOk && decoded.Packet!.Header.HasFlag(PacketHeaderFlags.ConnectRequest)) { connectRequest = decoded.Packet; break; } } Console.WriteLine($"[live] total packets received: {packetsReceived}"); Assert.True(packetsReceived > 0, "Server did not respond at all within 5s — is ACE actually running on " + $"{remote} and does the account '{user}' exist?"); Assert.NotNull(connectRequest); var opt = connectRequest!.Optional; Console.WriteLine($"[live] ConnectRequest decoded: " + $"serverTime={opt.ConnectRequestServerTime:F3} " + $"cookie=0x{opt.ConnectRequestCookie:X16} " + $"clientId=0x{opt.ConnectRequestClientId:X8} " + $"serverSeed=0x{opt.ConnectRequestServerSeed:X8} " + $"clientSeed=0x{opt.ConnectRequestClientSeed:X8}"); Assert.NotEqual(0UL, opt.ConnectRequestCookie); Assert.NotEqual(0u, opt.ConnectRequestClientId); // The seeds are derived from a PRNG server-side so they should be nonzero // in any realistic scenario (probability of exactly 0 is ~2^-64). Assert.NotEqual(0u, opt.ConnectRequestServerSeed); Assert.NotEqual(0u, opt.ConnectRequestClientSeed); } }