using System.Diagnostics; using System.Net; using System.Net.Sockets; using XFEExtension.NetCore.FormatExtension; using XFEExtension.NetCore.XFEConsole; internal static class RemoteDebugSelfTest { private const int MessageCount = 5_000; public static async Task RunAsync() { var port = GetAvailablePort(); await using var server = new XFEConsoleProgramServer(port, true, "correct-password", "RemoteTestProgram", "program-1"); await server.StartAsync(); await VerifyWrongPasswordIsRejectedAsync(port); var messagesPerSecond = await VerifyAuthenticatedOutputAsync(server, port); await VerifyStaticConsoleServerApiAsync(); await VerifyOriginalProgramClientModeAsync(); Console.WriteLine("PASS wrong password rejected"); Console.WriteLine("PASS authenticated terminal metadata"); Console.WriteLine($"PASS {MessageCount:N0} remote output messages at {messagesPerSecond:N0} messages/second"); Console.WriteLine("PASS XFEConsole.UseXFEConsoleServer output redirection"); Console.WriteLine("PASS original program-client mode compatibility"); } private static async Task VerifyWrongPasswordIsRejectedAsync(int port) { await using var terminal = new XFEConsoleTerminalClient($"ws://localhost:{port}/", "wrong-password", "RejectedTerminal", "terminal-rejected"); var accepted = await terminal.ConnectAsync(TimeSpan.FromSeconds(5)); if (accepted) throw new InvalidOperationException("The server accepted an incorrect password."); if (string.IsNullOrWhiteSpace(terminal.AuthenticationFailureReason)) throw new InvalidOperationException("The server did not return an authentication failure reason."); } private static async Task VerifyAuthenticatedOutputAsync(XFEConsoleProgramServer server, int port) { await using var terminal = new XFEConsoleTerminalClient($"ws://localhost:{port}/", "correct-password", "TestToolbox", "terminal-1"); var received = 0; var receivedAll = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); terminal.MessageReceived += (_, message) => { var dictionary = new XFEDictionary(message); if (dictionary["IsLine"] != "true" || dictionary["Text"] is null) receivedAll.TrySetException(new InvalidDataException("Received an invalid console output message.")); if (Interlocked.Increment(ref received) == MessageCount) receivedAll.TrySetResult(); }; if (!await terminal.ConnectAsync(TimeSpan.FromSeconds(5))) throw new InvalidOperationException($"Authentication failed: {terminal.AuthenticationFailureReason}"); if (terminal.RemoteProgramName != "RemoteTestProgram" || terminal.RemoteProgramId != "program-1") throw new InvalidOperationException("The authenticated program metadata is incorrect."); var stopwatch = Stopwatch.StartNew(); for (var index = 0; index < MessageCount; index++) await server.OutputMessage($"line-{index}", true); await receivedAll.Task.WaitAsync(TimeSpan.FromSeconds(20)); stopwatch.Stop(); if (received != MessageCount) throw new InvalidOperationException($"Expected {MessageCount} messages but received {received}."); return MessageCount / stopwatch.Elapsed.TotalSeconds; } private static async Task VerifyStaticConsoleServerApiAsync() { var port = GetAvailablePort(); var originalShowInDebug = XFEConsole.ShowInDebug; var originalShowInLocalConsole = XFEConsole.ShowInLocalConsole; XFEConsole.ShowInDebug = false; XFEConsole.ShowInLocalConsole = false; try { await XFEConsole.UseXFEConsoleServer(port, true, "static-password", "StaticProgram", "static-program"); await using var terminal = new XFEConsoleTerminalClient($"ws://localhost:{port}/", "static-password"); var received = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); terminal.MessageReceived += (_, message) => received.TrySetResult(message); if (!await terminal.ConnectAsync(TimeSpan.FromSeconds(5))) throw new InvalidOperationException("The static XFEConsole server rejected the correct password."); Console.WriteLine("static-server-output"); var dictionary = new XFEDictionary(await received.Task.WaitAsync(TimeSpan.FromSeconds(5))); if (dictionary["Text"]?.Contains("static-server-output", StringComparison.Ordinal) != true) throw new InvalidOperationException("Console.Out was not redirected to the program server."); } finally { await XFEConsole.StopXFEConsole(); XFEConsole.ShowInDebug = originalShowInDebug; XFEConsole.ShowInLocalConsole = originalShowInLocalConsole; } } private static async Task VerifyOriginalProgramClientModeAsync() { var port = GetAvailablePort(); var connected = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var received = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var server = new XFEConsoleTerminalServer(port, true, "original-password"); server.Connected += (_, _) => connected.TrySetResult(); server.MessageReceived += (_, message) => received.TrySetResult(message); await server.StartAsync(); try { var rejectedClosed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var rejectedClient = new XFEConsoleProgramClient($"ws://localhost:{port}/", "RejectedProgram", "rejected-program", "wrong-password"); rejectedClient.Client.ConnectionClosedHandler = cancellationToken => { rejectedClosed.TrySetResult(); return ValueTask.CompletedTask; }; await rejectedClient.Connect(); await rejectedClosed.Task.WaitAsync(TimeSpan.FromSeconds(5)); if (connected.Task.IsCompleted) throw new InvalidOperationException("The original terminal server authenticated an incorrect password."); var client = new XFEConsoleProgramClient($"ws://localhost:{port}/", "OriginalProgram", "original-program", "original-password"); if (!await client.Connect()) throw new InvalidOperationException("The original program client could not connect."); await connected.Task.WaitAsync(TimeSpan.FromSeconds(5)); await client.OutputMessage("original-mode-output", true); var dictionary = new XFEDictionary(await received.Task.WaitAsync(TimeSpan.FromSeconds(5))); if (dictionary["Text"] != "original-mode-output") throw new InvalidOperationException("The original program-client output was not preserved."); await client.Client.DisconnectAsync(); } finally { await server.StopAsync(); } } private static int GetAvailablePort() { using var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); return ((IPEndPoint)listener.LocalEndpoint).Port; } }