Skip to content

Commit

Permalink
Merge pull request #1 from HarryCordewener/Initial_MSDP
Browse files Browse the repository at this point in the history
Initial msdp draft
  • Loading branch information
HarryCordewener authored Jan 7, 2024
2 parents 49249c8 + 5d0237a commit d29ac0b
Show file tree
Hide file tree
Showing 20 changed files with 420 additions and 61 deletions.
17 changes: 8 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,20 @@ This library is in a state where breaking changes to the interface are expected.
| RFC | Description | Supported | Comments |
| ------------------------------------------- | ---------------------------------- |------------| ------------------ |
| http://www.faqs.org/rfcs/rfc855.html | Telnet Option Specification | Full | |
| http://www.faqs.org/rfcs/rfc858.html | Suppress GOAHEAD Negotiation | Full | Untested |
| http://www.faqs.org/rfcs/rfc1091.html | Terminal Type Negotiation | Full | |
| https://tintin.mudhalla.net/protocols/mtts | MTTS Negotiation (Extends TTYPE) | Full | |
| http://www.faqs.org/rfcs/rfc885.html | End Of Record Negotiation | Full | Untested |
| https://tintin.mudhalla.net/protocols/eor | End Of Record Negotiation | Full | Untested |
| http://www.faqs.org/rfcs/rfc1073.html | Window Size Negotiation (NAWS) | Full | |
| http://www.faqs.org/rfcs/rfc2066.html | Charset Negotiation | Partial | No TTABLE support |
| https://tintin.mudhalla.net/protocols/gmcp | Generic Mud Communication Protocol | Full | |
| https://tintin.mudhalla.net/protocols/mssp | MSSP Negotiation (Extents 855) | Full | Untested |
| http://www.faqs.org/rfcs/rfc885.html | End Of Record Negotiation | Full | Untested, Error |
| https://tintin.mudhalla.net/protocols/eor | End Of Record Negotiation | Full | Untested, Error |
| http://www.faqs.org/rfcs/rfc858.html | Suppress GOAHEAD Negotiation | Full | Untested |
| https://tintin.mudhalla.net/protocols/msdp | Mud Server Data Protocol | Partial | Planned |
| http://www.faqs.org/rfcs/rfc2066.html | Charset Negotiation | Partial | No TTABLE support |
| http://www.faqs.org/rfcs/rfc1572.html | New Environment Negotiation | No | Planned |
| https://tintin.mudhalla.net/protocols/mnes | Mud New Environment Negotiation | No | Planned |
| https://tintin.mudhalla.net/protocols/msdp | Mud Server Data Protocol | No | Planned |
| https://tintin.mudhalla.net/rfc/rfc1950/ | ZLIB Compression | No | Planned |
| https://tintin.mudhalla.net/protocols/mccp | Mud Client Compression Protocol | No | Planned |
| https://tintin.mudhalla.net/protocols/gmcp | Generic Mud Communication Protocol | Partial | MSDP Planned |
| https://tintin.mudhalla.net/protocols/mccp | Mud Client Compression Protocol | No | Planned |
| https://tintin.mudhalla.net/protocols/mccp | Mud Client Compression Protocol | No | Rejects |
| https://tintin.mudhalla.net/rfc/rfc1950/ | ZLIB Compression | No | Rejects |
| http://www.faqs.org/rfcs/rfc857.html | Echo Negotiation | No | Rejects |
| http://www.faqs.org/rfcs/rfc1079.html | Terminal Speed Negotiation | No | Rejects |
| http://www.faqs.org/rfcs/rfc1372.html | Flow Control Negotiation | No | Rejects |
Expand Down
45 changes: 45 additions & 0 deletions TelnetNegotiationCore.Functional/MSDPLibrary.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace TelnetNegotiationCore.Functional

open System.Text

module MSDPLibrary =
type Trigger =
| NULL = 0uy
| MSDP_VAR = 1uy
| MSDP_VAL = 2uy
| MSDP_TABLE_OPEN = 3uy
| MSDP_TABLE_CLOSE = 4uy
| MSDP_ARRAY_OPEN = 5uy
| MSDP_ARRAY_CLOSE = 6uy

[<TailCall>]
let private MSDPScanTailRec (root: obj, array: seq<byte>, encoding: Encoding) =
let rec scan accRoot accArray =
if Seq.length accArray = 0 then (accRoot, accArray)
else
match accArray |> Seq.head with
| 1uy ->
let key = encoding.GetString(accArray |> Seq.skip(1) |> Seq.takeWhile(fun x -> x <> byte Trigger.MSDP_VAL) |> Array.ofSeq)
let (calculatedValue, leftoverArray) = scan root (accArray |> Seq.skip(1) |> Seq.skipWhile(fun x -> x <> byte Trigger.MSDP_VAL))
scan ((accRoot :?> Map<string, obj>).Add(key, calculatedValue)) leftoverArray
| 2uy ->
if accRoot :? Map<string, obj> then
scan (Map<string, obj> []) (accArray |> Seq.skip(1))
elif accRoot :? List<obj> then
let (calculatedValue, leftoverArray) = scan (Map<string, obj> []) (accArray |> Seq.skip(1))
scan ((accRoot :?> List<obj>) @ [calculatedValue]) leftoverArray
else
scan accRoot (accArray |> Seq.skip(1))
| 3uy ->
scan (Map<string, obj> []) (accArray |> Seq.skip(1))
| 5uy ->
scan (List<obj>.Empty) (accArray |> Seq.skip(1))
| 4uy | 6uy ->
(accRoot, accArray |> Seq.skip(1))
| _ ->
(encoding.GetString(accArray |> Seq.takeWhile(fun x -> x > 6uy) |> Array.ofSeq), accArray |> Seq.skipWhile(fun x -> x > 6uy))
scan root array

let public MSDPScan(array: seq<byte>, encoding) =
let (result, _) = MSDPScanTailRec(Map<string,obj> [], array, encoding)
result
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<Compile Include="MSDPLibrary.fs" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion TelnetNegotiationCore.TestServer/KestrelMockServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace TelnetNegotiationCore.TestServer
{
public class KestrelMockServer : ConnectionHandler
{
private ILogger _Logger;
private readonly ILogger _Logger;

public KestrelMockServer(ILogger logger = null): base()
{
Expand Down
18 changes: 18 additions & 0 deletions TelnetNegotiationCore.UnitTests/BaseTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Serilog;

namespace TelnetNegotiationCore.UnitTests
{
public class BaseTest
{
static BaseTest()
{
var log = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] ({TelnetMode}) {Message:lj}{NewLine}{Exception}")
.MinimumLevel.Verbose()
.CreateLogger();

Log.Logger = log;
}
}
}
9 changes: 1 addition & 8 deletions TelnetNegotiationCore.UnitTests/CHARSETTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace TelnetNegotiationCore.UnitTests
{
[TestFixture]
public class CHARSETTests
public class CHARSETTests: BaseTest
{
private byte[] _negotiationOutput;

Expand All @@ -28,13 +28,6 @@ public void Setup()
{
_negotiationOutput = null;

var log = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] ({TelnetMode}) {Message:lj}{NewLine}{Exception}")
.MinimumLevel.Verbose()
.CreateLogger();

Log.Logger = log;
}

[TestCaseSource(nameof(ServerCHARSETSequences), Category = nameof(TelnetInterpreter.TelnetMode.Server))]
Expand Down
3 changes: 2 additions & 1 deletion TelnetNegotiationCore.UnitTests/CreateDotGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Stateless.Graph;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using TelnetNegotiationCore.Interpreters;
Expand All @@ -13,7 +14,7 @@ namespace TelnetNegotiationCore.UnitTests
[TestFixture(
Category = "Tool",
Description = "Creates the DotGraph files for Server and Client forms. Some of these are combined.")]
public class CreateDotGraph
public class CreateDotGraph : BaseTest
{
readonly ILogger _Logger;

Expand Down
84 changes: 84 additions & 0 deletions TelnetNegotiationCore.UnitTests/MSDPTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using NUnit.Framework;
using Serilog;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using TelnetNegotiationCore.Models;

namespace TelnetNegotiationCore.UnitTests
{
[TestFixture]
public class MSDPTests : BaseTest
{
static Encoding encoding = Encoding.ASCII;

[TestCaseSource(nameof(FSharpTestSequences))]
public void TestFSharp(byte[] testcase, dynamic expectedObject)
{
var result = Functional.MSDPLibrary.MSDPScan(testcase, encoding);
Log.Logger.Information("Serialized: {NewLine} {Serialized}", JsonSerializer.Serialize(result));
Assert.AreEqual(JsonSerializer.Serialize(expectedObject), JsonSerializer.Serialize(result));
}

public static IEnumerable<TestCaseData> FSharpTestSequences
{
get
{
yield return new TestCaseData((byte[])[
(byte)Trigger.MSDP_VAR,
.. encoding.GetBytes("LIST"),
(byte)Trigger.MSDP_VAL,
.. encoding.GetBytes("COMMANDS")],
new { LIST = "COMMANDS" });

yield return new TestCaseData((byte[])[
(byte)Trigger.MSDP_VAR,
.. encoding.GetBytes("COMMANDS"),
(byte)Trigger.MSDP_VAL,
(byte)Trigger.MSDP_ARRAY_OPEN,
(byte)Trigger.MSDP_VAL,
.. encoding.GetBytes("LIST"),
(byte)Trigger.MSDP_VAL,
.. encoding.GetBytes("REPORT"),
(byte)Trigger.MSDP_VAL,
.. encoding.GetBytes("SEND"),
(byte)Trigger.MSDP_ARRAY_CLOSE],
new { COMMANDS = (string[])["LIST", "REPORT", "SEND"] });

yield return new TestCaseData((byte[])[
(byte)Trigger.MSDP_VAR,
.. encoding.GetBytes("ROOM"),
(byte)Trigger.MSDP_VAL,
(byte)Trigger.MSDP_TABLE_OPEN,
(byte)Trigger.MSDP_VAR,
.. encoding.GetBytes("VNUM"),
(byte)Trigger.MSDP_VAL,
.. encoding.GetBytes("6008"),
(byte)Trigger.MSDP_VAR,
.. encoding.GetBytes("NAME"),
(byte)Trigger.MSDP_VAL,
.. encoding.GetBytes("The Forest clearing"),
(byte)Trigger.MSDP_VAR,
.. encoding.GetBytes("AREA"),
(byte)Trigger.MSDP_VAL,
.. encoding.GetBytes("Haon Dor"),
(byte)Trigger.MSDP_VAR,
.. encoding.GetBytes("EXITS"),
(byte)Trigger.MSDP_VAL,
(byte)Trigger.MSDP_TABLE_OPEN,
(byte)Trigger.MSDP_VAR,
.. encoding.GetBytes("n"),
(byte)Trigger.MSDP_VAL,
.. encoding.GetBytes("6011"),
(byte)Trigger.MSDP_VAR,
.. encoding.GetBytes("e"),
(byte)Trigger.MSDP_VAL,
.. encoding.GetBytes("6012"),
(byte)Trigger.MSDP_TABLE_CLOSE,
(byte)Trigger.MSDP_TABLE_CLOSE
],
new { ROOM = new { AREA = "Haon Dor", EXITS = new { e = "6012", n = "6011" }, NAME = "The Forest clearing", VNUM = "6008" } });
}
}
}
}
8 changes: 0 additions & 8 deletions TelnetNegotiationCore.UnitTests/TTypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,6 @@ public class TTypeTests
[SetUp]
public async Task Setup()
{
var log = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console()
.MinimumLevel.Verbose()
.CreateLogger();

Log.Logger = log;

_server_ti = await new TelnetInterpreter(TelnetInterpreter.TelnetMode.Server)
{
CallbackNegotiationAsync = WriteBackToNegotiate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<TargetFramework>net8.0</TargetFramework>

<IsPackable>false</IsPackable>

<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
Expand Down
6 changes: 6 additions & 0 deletions TelnetNegotiationCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TelnetNegotiationCore.UnitT
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TelnetNegotiationCore.TestClient", "TelnetNegotiationCore.TestClient\TelnetNegotiationCore.TestClient.csproj", "{E1A76C78-8246-472F-824E-9F7F9B3778B7}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TelnetNegotiationCore.Functional", "TelnetNegotiationCore.Functional\TelnetNegotiationCore.Functional.fsproj", "{0307D0B8-AAA7-40C8-9E3E-34BE64F93BB7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -41,6 +43,10 @@ Global
{E1A76C78-8246-472F-824E-9F7F9B3778B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1A76C78-8246-472F-824E-9F7F9B3778B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1A76C78-8246-472F-824E-9F7F9B3778B7}.Release|Any CPU.Build.0 = Release|Any CPU
{0307D0B8-AAA7-40C8-9E3E-34BE64F93BB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0307D0B8-AAA7-40C8-9E3E-34BE64F93BB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0307D0B8-AAA7-40C8-9E3E-34BE64F93BB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0307D0B8-AAA7-40C8-9E3E-34BE64F93BB7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
22 changes: 11 additions & 11 deletions TelnetNegotiationCore/Interpreters/TelnetEORInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,31 +113,31 @@ private async Task OnWillEORAsync(StateMachine<State, Trigger>.Transition _)
}

/// <summary>
/// Sends a byte message as a Prompt, adding EOR if desired.
/// Sends a byte message as a Prompt, if supported, by not sending an EOR at the end.
/// </summary>
/// <param name="send">Byte array</param>
/// <returns>A completed Task</returns>
public async Task SendPromptAsync(byte[] send)
{
if (_doEOR is null or false)
{
await CallbackNegotiationAsync(send);
}
else
{
await CallbackNegotiationAsync([(byte)Trigger.IAC, (byte)Trigger.EOR]);
await CallbackNegotiationAsync(send);
}
}

/// <summary>
/// Sends a byte message as a Prompt, adding EOR if desired.
/// Sends a byte message, adding an EOR at the end if needed.
/// </summary>
/// <param name="send">Byte array</param>
/// <returns>A completed Task</returns>
public async Task SendAsync(byte[] send)
{
await CallbackNegotiationAsync(send);
if (_doEOR is null or false)
{
await CallbackNegotiationAsync(send);
}
else
{
await CallbackNegotiationAsync(send);
await CallbackNegotiationAsync([(byte)Trigger.IAC, (byte)Trigger.EOR]);
}
}
}
}
11 changes: 9 additions & 2 deletions TelnetNegotiationCore/Interpreters/TelnetGMCPInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using OneOf;
using MoreLinq;
using System.Linq;
using System.Text.Json;

namespace TelnetNegotiationCore.Interpreters
{
Expand Down Expand Up @@ -121,9 +122,15 @@ private async Task CompleteGMCPNegotiation(StateMachine<State, Trigger>.Transiti
var firstSpace = _GMCPBytes.FindIndex(x => x == space);
var packageBytes = _GMCPBytes.Take(firstSpace).ToArray();
var rest = _GMCPBytes.Skip(firstSpace + 1).ToArray();
// TODO: Check for MSDP information, if so, convert to JSON first, then send as Info.

// TODO: Consideration: a version of this that sends back a Dynamic or other similar object.
await (SignalOnGMCPAsync?.Invoke((Package: CurrentEncoding.GetString(packageBytes), Info: CurrentEncoding.GetString(packageBytes))) ?? Task.CompletedTask);
var package = CurrentEncoding.GetString(packageBytes);
var info =
package == "MSDP"
? JsonSerializer.Serialize(Functional.MSDPLibrary.MSDPScan(packageBytes, CurrentEncoding))
: CurrentEncoding.GetString(packageBytes);

await (SignalOnGMCPAsync?.Invoke((Package: package, Info: info)) ?? Task.CompletedTask);
}

/// <summary>
Expand Down
Loading

0 comments on commit d29ac0b

Please sign in to comment.