From f4c06f038b085def00d2323963713a029d44ba37 Mon Sep 17 00:00:00 2001 From: Harry Cordewener Date: Wed, 3 Jan 2024 19:41:35 -0600 Subject: [PATCH] Implement test client as using C# Pipeline. --- README.md | 16 +-- .../MockClient.cs | 108 ------------------ .../MockPipelineClient.cs | 102 +++++++++++++++++ TelnetNegotiationCore.TestClient/Program.cs | 7 +- .../TelnetNegotiationCore.TestClient.csproj | 4 +- .../MockServer.cs | 2 + .../TelnetNegotiationCore.TestServer.csproj | 2 +- .../Interpreters/TelnetStandardInterpreter.cs | 16 +++ 8 files changed, 135 insertions(+), 122 deletions(-) delete mode 100644 TelnetNegotiationCore.TestClient/MockClient.cs create mode 100644 TelnetNegotiationCore.TestClient/MockPipelineClient.cs diff --git a/README.md b/README.md index b958bd3..426061d 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Initiate a logger. A Serilog logger is required by this library at this time. Log.Logger = log; ``` -Create functions that implement your desired behavior on getting as signal. +Create functions that implement your desired behavior on getting a signal. ```csharp private async Task WriteToOutputStreamAsync(byte[] arg, StreamWriter writer) { @@ -92,13 +92,13 @@ Initialize the Interpreter. ```csharp var telnet = new TelnetInterpreter(TelnetInterpreter.TelnetMode.Client, _Logger.ForContext()) { - CallbackOnSubmitAsync = WriteBackAsync, - CallbackNegotiationAsync = (x) => WriteToOutputStreamAsync(x, output), - SignalOnGMCPAsync = SignalGMCPAsync, - SignalOnMSSPAsync = SignalMSSPAsync, - SignalOnNAWSAsync = SignalNAWSAsync, - SignalOnPromptingAsync = SignalPromptAsync, - CharsetOrder = new[] { Encoding.GetEncoding("utf-8"), Encoding.GetEncoding("iso-8859-1") } + CallbackOnSubmitAsync = WriteBackAsync, + CallbackNegotiationAsync = (x) => WriteToOutputStreamAsync(x, output), + SignalOnGMCPAsync = SignalGMCPAsync, + SignalOnMSSPAsync = SignalMSSPAsync, + SignalOnNAWSAsync = SignalNAWSAsync, + SignalOnPromptingAsync = SignalPromptAsync, + CharsetOrder = new[] { Encoding.GetEncoding("utf-8"), Encoding.GetEncoding("iso-8859-1") } }.BuildAsync(); ``` diff --git a/TelnetNegotiationCore.TestClient/MockClient.cs b/TelnetNegotiationCore.TestClient/MockClient.cs deleted file mode 100644 index 5e7d3a2..0000000 --- a/TelnetNegotiationCore.TestClient/MockClient.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Serilog; -using System.Net; -using System.Net.Sockets; -using System.Text; -using TelnetNegotiationCore.Interpreters; -using TelnetNegotiationCore.Models; - -namespace TelnetNegotiationCore.TestClient -{ - public class MockClient - { - readonly ILogger _Logger; - readonly TcpClient? client = null; - TelnetInterpreter? telnet = null; - - public MockClient(string address, int port, ILogger? logger = null) - { - Console.InputEncoding = Console.OutputEncoding = Encoding.UTF8; - - _Logger = logger ?? Log.Logger.ForContext(); - client = new TcpClient(address, port); - } - - public async Task StartAsync() - { - var t = new Thread(new ParameterizedThreadStart(Handle)); - t.Start(client); - - while (true) - { - var read = Console.ReadLine() + Environment.NewLine; - - if(telnet != null) - { - await telnet.SendPromptAsync(telnet?.CurrentEncoding.GetBytes(read)); - } - } - } - - private async Task WriteToOutputStreamAsync(byte[] arg, StreamWriter writer) - { - try - { - await writer.BaseStream.WriteAsync(arg, CancellationToken.None); - } - catch(ObjectDisposedException ode) - { - _Logger.Information("Stream has been closed", ode); - } - } - - public static Task WriteBackAsync(byte[] writeback, Encoding encoding) => - Task.Run(() => Console.WriteLine(encoding.GetString(writeback))); - - public Task SignalGMCPAsync((string module, string writeback) val, Encoding encoding) => - Task.Run(() => _Logger.Debug("GMCP Signal: {Module}: {WriteBack}", val.module, val.writeback)); - - public Task SignalMSSPAsync(MSSPConfig val) => - Task.Run(() => _Logger.Debug("New MSSP: {@MSSP}", val)); - - public Task SignalPromptAsync() => - Task.Run(() => _Logger.Debug("Prompt")); - - public Task SignalNAWSAsync(int height, int width) => - Task.Run(() => _Logger.Debug("Client Height and Width updated: {Height}x{Width}", height, width)); - - public void Handle(object? obj) - { - var client = (TcpClient)obj!; - int port = -1; - - try - { - port = ((IPEndPoint)client.Client.RemoteEndPoint!).Port; - - using var stream = client.GetStream(); - using var input = new StreamReader(stream); - using var output = new StreamWriter(stream, leaveOpen: true) { AutoFlush = true }; - - telnet = new TelnetInterpreter(TelnetInterpreter.TelnetMode.Client, _Logger.ForContext()) - { - CallbackOnSubmitAsync = WriteBackAsync, - CallbackNegotiationAsync = (x) => WriteToOutputStreamAsync(x, output), - SignalOnGMCPAsync = SignalGMCPAsync, - SignalOnMSSPAsync = SignalMSSPAsync, - SignalOnNAWSAsync = SignalNAWSAsync, - SignalOnPromptingAsync = SignalPromptAsync, - CharsetOrder = new[] { Encoding.GetEncoding("utf-8"), Encoding.GetEncoding("iso-8859-1") } - }.BuildAsync() - .GetAwaiter() - .GetResult(); - - _Logger.Information("Connection: {ConnectionState}", "Connected"); - - for (int currentByte = 0; currentByte != -1; currentByte = input.BaseStream.ReadByte()) - { - telnet.InterpretAsync((byte)currentByte).GetAwaiter().GetResult(); - } - - _Logger.Information("Connection: {ConnectionState}", "Connection Closed"); - } - catch (Exception ex) - { - _Logger.Error(ex, "Connection: {ConnectionState}", "Connection was unexpectedly closed."); - } - } - } -} diff --git a/TelnetNegotiationCore.TestClient/MockPipelineClient.cs b/TelnetNegotiationCore.TestClient/MockPipelineClient.cs new file mode 100644 index 0000000..8c235ae --- /dev/null +++ b/TelnetNegotiationCore.TestClient/MockPipelineClient.cs @@ -0,0 +1,102 @@ +using Serilog; +using System.Net.Sockets; +using TelnetNegotiationCore.Interpreters; +using System.IO.Pipelines; +using Pipelines.Sockets.Unofficial; +using System.Buffers; +using System.Text; +using TelnetNegotiationCore.Models; +using System.Net; +using System.Collections.Immutable; + +namespace TelnetNegotiationCore.TestClient +{ + public class MockPipelineClient + { + readonly ILogger _Logger; + + public MockPipelineClient() + { + _Logger = Log.Logger.ForContext(); + } + + private async Task WriteToOutputStreamAsync(byte[] arg, PipeWriter writer) + { + try + { + await writer.WriteAsync(new ReadOnlyMemory(arg), CancellationToken.None); + } + catch (ObjectDisposedException ode) + { + _Logger.Information("Stream has been closed", ode); + } + } + + public static Task WriteBackAsync(byte[] writeback, Encoding encoding) => + Task.Run(() => Console.WriteLine(encoding.GetString(writeback))); + + public Task SignalGMCPAsync((string module, string writeback) val, Encoding encoding) => + Task.Run(() => _Logger.Debug("GMCP Signal: {Module}: {WriteBack}", val.module, val.writeback)); + + public Task SignalMSSPAsync(MSSPConfig val) => + Task.Run(() => _Logger.Debug("New MSSP: {@MSSP}", val)); + + public Task SignalPromptAsync() => + Task.Run(() => _Logger.Debug("Prompt")); + + public Task SignalNAWSAsync(int height, int width) => + Task.Run(() => _Logger.Debug("Client Height and Width updated: {Height}x{Width}", height, width)); + + public async Task StartAsync(string address, int port) + { + var client = new TcpClient(address, port); + var stream = client.GetStream(); + var pipe = StreamConnection.GetDuplex(stream, new PipeOptions()); + + var telnet = await new TelnetInterpreter(TelnetInterpreter.TelnetMode.Client, _Logger.ForContext()) + { + CallbackOnSubmitAsync = WriteBackAsync, + CallbackNegotiationAsync = (x) => WriteToOutputStreamAsync(x, pipe.Output), + SignalOnGMCPAsync = SignalGMCPAsync, + SignalOnMSSPAsync = SignalMSSPAsync, + SignalOnNAWSAsync = SignalNAWSAsync, + SignalOnPromptingAsync = SignalPromptAsync, + CharsetOrder = new[] { Encoding.GetEncoding("utf-8"), Encoding.GetEncoding("iso-8859-1") } + }.BuildAsync(); + + var backgroundTask = Task.Run(() => ReadFromPipeline(telnet, pipe.Input)); + + while (true) + { + var read = Console.ReadLine() + Environment.NewLine; + + if (telnet != null) + { + await telnet.SendPromptAsync(telnet?.CurrentEncoding.GetBytes(read)); + } + } + } + + /// + /// Read data coming from the server and interpret it. + /// + /// The Pipeline Reader + /// An awaitable result + static async ValueTask ReadFromPipeline(TelnetInterpreter telnet, PipeReader reader) + { + while (true) + { + // await some data being available + ReadResult read = await reader.ReadAtLeastAsync(1); + + foreach(var segment in read.Buffer) + { + await telnet.InterpretByteArrayAsync(segment.Span.ToImmutableArray()); + } + + // tell the pipe that we used everything + reader.AdvanceTo(read.Buffer.End); + } + } + } +} diff --git a/TelnetNegotiationCore.TestClient/Program.cs b/TelnetNegotiationCore.TestClient/Program.cs index 4a54b88..0c2274b 100644 --- a/TelnetNegotiationCore.TestClient/Program.cs +++ b/TelnetNegotiationCore.TestClient/Program.cs @@ -9,15 +9,14 @@ static async Task Main() { var log = new LoggerConfiguration() .Enrich.FromLogContext() - .WriteTo.File(new CompactJsonFormatter(), "logresult.log") + .WriteTo.File(new CompactJsonFormatter(), "LogResult.log") .WriteTo.Console() .MinimumLevel.Debug() .CreateLogger(); Log.Logger = log; - var client = new MockClient("127.0.0.1", 4202, log.ForContext()); - - await client.StartAsync(); + var client = new MockPipelineClient(); + await client.StartAsync("127.0.0.1", 4201); } } } \ No newline at end of file diff --git a/TelnetNegotiationCore.TestClient/TelnetNegotiationCore.TestClient.csproj b/TelnetNegotiationCore.TestClient/TelnetNegotiationCore.TestClient.csproj index 539aa56..cb8bea3 100644 --- a/TelnetNegotiationCore.TestClient/TelnetNegotiationCore.TestClient.csproj +++ b/TelnetNegotiationCore.TestClient/TelnetNegotiationCore.TestClient.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 enable enable @@ -16,7 +16,9 @@ + + diff --git a/TelnetNegotiationCore.TestServer/MockServer.cs b/TelnetNegotiationCore.TestServer/MockServer.cs index 19417db..45fc3fe 100644 --- a/TelnetNegotiationCore.TestServer/MockServer.cs +++ b/TelnetNegotiationCore.TestServer/MockServer.cs @@ -103,6 +103,8 @@ public void HandleDevice(object obj) Clients.TryAdd(port, telnet); + + _Logger.Information("Connection: {ConnectionState}", "Connected"); for (int currentByte = 0; currentByte != -1; currentByte = input.BaseStream.ReadByte()) diff --git a/TelnetNegotiationCore.TestServer/TelnetNegotiationCore.TestServer.csproj b/TelnetNegotiationCore.TestServer/TelnetNegotiationCore.TestServer.csproj index 1431100..0f1e848 100644 --- a/TelnetNegotiationCore.TestServer/TelnetNegotiationCore.TestServer.csproj +++ b/TelnetNegotiationCore.TestServer/TelnetNegotiationCore.TestServer.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 diff --git a/TelnetNegotiationCore/Interpreters/TelnetStandardInterpreter.cs b/TelnetNegotiationCore/Interpreters/TelnetStandardInterpreter.cs index 9c910a9..4c6c97d 100644 --- a/TelnetNegotiationCore/Interpreters/TelnetStandardInterpreter.cs +++ b/TelnetNegotiationCore/Interpreters/TelnetStandardInterpreter.cs @@ -7,6 +7,7 @@ using TelnetNegotiationCore.Models; using MoreLinq; using OneOf; +using System.Collections.Immutable; namespace TelnetNegotiationCore.Interpreters { @@ -255,5 +256,20 @@ public async Task InterpretAsync(byte bt) await TelnetStateMachine.FireAsync(ParameterizedTrigger(Trigger.ReadNextCharacter), bt); } } + + + /// + /// Interprets the next byte in an asynchronous way. + /// TODO: Cache the value of IsDefined, or get a way to compile this down to a faster call that doesn't require reflection each time. + /// + /// An integer representation of a byte. + /// Awaitable Task + public async Task InterpretByteArrayAsync(ImmutableArray byteArray) + { + foreach (var b in byteArray) + { + await InterpretAsync(b); + } + } } }