Skip to content

Commit

Permalink
Update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
HarryCordewener committed Jan 30, 2024
1 parent 0c1f75a commit 4ca762d
Showing 1 changed file with 173 additions and 53 deletions.
226 changes: 173 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

# Telnet Negotiation Core
## Summary
This project is intended to be a library that implements basic telnet functionality.
This is done with an eye on MUDs at this time, but may improve to support more terminal capabilities as time permits.
This project provides a library that implements telnet functionality, and as many of its RFCs are viable, in a testable manner.
This is done with an eye on MUDs at this time, but may improve to support more terminal capabilities as time permits and if there is ask for it.

At this time, this repository is in a rough state and does not yet implement some common modern code standards.
At this time, this repository is in a rough state and does not yet implement some common modern code standards, but is constantly evolving.

## State
This library is in a state where breaking changes to the interface are expected.
Expand Down Expand Up @@ -43,71 +43,191 @@ Being a Telnet Negotiation Library, this library doesn't give support for extens

## Use
### Client
A messy example exists in the TestClient Project.
A documented example exists in the [TestClient Project](TelnetNegotiationCore.TestClient/MockPipelineClient.cs).

Initiate a logger. A Serilog logger is required by this library at this time.
```csharp
var log = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File(new CompactJsonFormatter(), "LogResult.log")
.MinimumLevel.LogDebug()
.CreateLogger();

Log.Logger = log;
var log = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File(new CompactJsonFormatter(), "LogResult.log")
.MinimumLevel.LogDebug()
.CreateLogger();

Log.Logger = log;
```

Create functions that implement your desired behavior on getting a signal.
```csharp
private async Task WriteToOutputStreamAsync(byte[] arg, StreamWriter writer)
{
try
{
await writer.BaseStream.WriteAsync(arg, CancellationToken.None);
}
catch(ObjectDisposedException ode)
{
_Logger.LogInformation("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.LogDebug("GMCP Signal: {Module}: {WriteBack}", val.module, val.writeback));

public Task SignalMSSPAsync(MSSPConfig val) =>
Task.Run(() => _Logger.LogDebug("New MSSP: {@MSSP}", val));

public Task SignalPromptAsync() =>
Task.Run(() => _Logger.LogDebug("Prompt"));

public Task SignalNAWSAsync(int height, int width) =>
Task.Run(() => _Logger.LogDebug("Client Height and Width updated: {Height}x{Width}", height, width));
private async Task WriteToOutputStreamAsync(byte[] arg, StreamWriter writer)
{
try
{
await writer.BaseStream.WriteAsync(arg, CancellationToken.None);
}
catch(ObjectDisposedException ode)
{
_Logger.LogInformation("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.LogDebug("GMCP Signal: {Module}: {WriteBack}", val.module, val.writeback));

public Task SignalMSSPAsync(MSSPConfig val) =>
Task.Run(() => _Logger.LogDebug("New MSSP: {@MSSP}", val));

public Task SignalPromptAsync() =>
Task.Run(() => _Logger.LogDebug("Prompt"));

public Task SignalNAWSAsync(int height, int width) =>
Task.Run(() => _Logger.LogDebug("Client Height and Width updated: {Height}x{Width}", height, width));
```

Initialize the Interpreter.
```csharp
var telnet = new TelnetInterpreter(TelnetInterpreter.TelnetMode.Client, _Logger.ForContext<TelnetInterpreter>())
{
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();
var telnet = new TelnetInterpreter(TelnetInterpreter.TelnetMode.Client, _Logger.ForContext<TelnetInterpreter>())
{
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();
```

Start interpreting.
```csharp
for (int currentByte = 0; currentByte != -1; currentByte = input.BaseStream.ReadByte())
{
telnet.InterpretAsync((byte)currentByte).GetAwaiter().GetResult();
}
for (int currentByte = 0; currentByte != -1; currentByte = input.BaseStream.ReadByte())
{
telnet.InterpretAsync((byte)currentByte).GetAwaiter().GetResult();
}
```

### Server
A messy example exists in the TestServer Project.
A documented example exists in the [TestServer Project](TelnetNegotiationCore.TestServer/KestrelMockServer.cs).
This uses a Kestrel server to make the TCP handling easier.
```csharp
public class KestrelMockServer : ConnectionHandler
{
private readonly ILogger _Logger;

public KestrelMockServer(ILogger<KestrelMockServer> logger) : base()
{
Console.OutputEncoding = Encoding.UTF8;
_Logger = logger;
}

private async Task WriteToOutputStreamAsync(byte[] arg, PipeWriter writer)
{
try
{
await writer.WriteAsync(new ReadOnlyMemory<byte>(arg), CancellationToken.None);
}
catch (ObjectDisposedException ode)
{
_Logger.LogError(ode, "Stream has been closed");
}
}

public Task SignalGMCPAsync((string module, string writeback) val)
{
_Logger.LogDebug("GMCP Signal: {Module}: {WriteBack}", val.module, val.writeback);
return Task.CompletedTask;
}

public Task SignalMSSPAsync(MSSPConfig val)
{
_Logger.LogDebug("New MSSP: {@MSSPConfig}", val);
return Task.CompletedTask;
}

public Task SignalNAWSAsync(int height, int width)
{
_Logger.LogDebug("Client Height and Width updated: {Height}x{Width}", height, width);
return Task.CompletedTask;
}

private static async Task SignalMSDPAsync(MSDPServerHandler handler, TelnetInterpreter telnet, string config) =>
await handler.HandleAsync(telnet, config);

public static async Task WriteBackAsync(byte[] writeback, Encoding encoding, TelnetInterpreter telnet)
{
var str = encoding.GetString(writeback);
if (str.StartsWith("echo"))
{
await telnet.SendAsync(encoding.GetBytes($"We heard: {str}" + Environment.NewLine));
}
Console.WriteLine(encoding.GetString(writeback));
}

private async Task MSDPUpdateBehavior(string resetVariable)
{
_Logger.LogDebug("MSDP Reset Request: {@Reset}", resetVariable);
await Task.CompletedTask;
}

public async override Task OnConnectedAsync(ConnectionContext connection)
{
using (_Logger.BeginScope(new Dictionary<string, object> { { "ConnectionId", connection.ConnectionId } }))
{
_Logger.LogInformation("{ConnectionId} connected", connection.ConnectionId);

var MSDPHandler = new MSDPServerHandler(new MSDPServerModel(MSDPUpdateBehavior)
{
Commands = () => ["help", "stats", "info"],
Configurable_Variables = () => ["CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID"],
Reportable_Variables = () => ["ROOM"],
Sendable_Variables = () => ["ROOM"],
});

var telnet = await new TelnetInterpreter(TelnetInterpreter.TelnetMode.Server, _Logger)
{
CallbackOnSubmitAsync = WriteBackAsync,
SignalOnGMCPAsync = SignalGMCPAsync,
SignalOnMSSPAsync = SignalMSSPAsync,
SignalOnNAWSAsync = SignalNAWSAsync,
SignalOnMSDPAsync = (telnet, config) => SignalMSDPAsync(MSDPHandler, telnet, config),
CallbackNegotiationAsync = (x) => WriteToOutputStreamAsync(x, connection.Transport.Output),
CharsetOrder = new[] { Encoding.GetEncoding("utf-8"), Encoding.GetEncoding("iso-8859-1") }
}
.RegisterMSSPConfig(() => new MSSPConfig
{
Name = "My Telnet Negotiated Server",
UTF_8 = true,
Gameplay = ["ABC", "DEF"],
Extended = new Dictionary<string, dynamic>
{
{ "Foo", "Bar"},
{ "Baz", (string[])["Moo", "Meow"] }
}
})
.BuildAsync();

while (true)
{
var result = await connection.Transport.Input.ReadAsync();
var buffer = result.Buffer;

foreach (var segment in buffer)
{
await telnet.InterpretByteArrayAsync(segment.Span.ToImmutableArray());
}

if (result.IsCompleted)
{
break;
}

connection.Transport.Input.AdvanceTo(buffer.End);
}
_Logger.LogInformation("{ConnectionId} disconnected", connection.ConnectionId);
}
}
}
```

0 comments on commit 4ca762d

Please sign in to comment.