diff --git a/README.md b/README.md index 029299d..88e9a86 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ This library is in a state where breaking changes to the interface are expected. | [MSSP](https://tintin.mudhalla.net/protocols/mssp) | MSSP Negotiation | Full | Untested | | [RFC 885](http://www.faqs.org/rfcs/rfc885.html) | End Of Record Negotiation | Full | Untested | | [EOR](https://tintin.mudhalla.net/protocols/eor) | End Of Record Negotiation | Full | Untested | -| [MSDP](https://tintin.mudhalla.net/protocols/msdp) | Mud Server Data Protocol | Partial | Planned | +| [MSDP](https://tintin.mudhalla.net/protocols/msdp) | Mud Server Data Protocol | Partial | Partial Tested | | [RFC 2066](http://www.faqs.org/rfcs/rfc2066.html) | Charset Negotiation | Partial | No TTABLE support | -| [RFC 858](http://www.faqs.org/rfcs/rfc858.html) | Suppress GOAHEAD Negotiation | No | Planned | +| [RFC 858](http://www.faqs.org/rfcs/rfc858.html) | Suppress GOAHEAD Negotiation | Full | Untested | | [RFC 1572](http://www.faqs.org/rfcs/rfc1572.html) | New Environment Negotiation | No | Planned | | [MNES](https://tintin.mudhalla.net/protocols/mnes) | Mud New Environment Negotiation | No | Planned | | [MCCP](https://tintin.mudhalla.net/protocols/mccp) | Mud Client Compression Protocol | No | Rejects | diff --git a/TelnetNegotiationCore/Interpreters/TelnetEORInterpreter.cs b/TelnetNegotiationCore/Interpreters/TelnetEORInterpreter.cs index b365791..7698d44 100644 --- a/TelnetNegotiationCore/Interpreters/TelnetEORInterpreter.cs +++ b/TelnetNegotiationCore/Interpreters/TelnetEORInterpreter.cs @@ -130,6 +130,11 @@ public async Task SendPromptAsync(byte[] send) { await CallbackNegotiationAsync([(byte)Trigger.IAC, (byte)Trigger.EOR]); } + // TODO: Tie into _doGA + if(_doEOR is not null or false) + { + await CallbackNegotiationAsync([(byte)Trigger.IAC, (byte)Trigger.GA]); + } } /// diff --git a/TelnetNegotiationCore/Interpreters/TelnetStandardInterpreter.cs b/TelnetNegotiationCore/Interpreters/TelnetStandardInterpreter.cs index f8bbccf..5102649 100644 --- a/TelnetNegotiationCore/Interpreters/TelnetStandardInterpreter.cs +++ b/TelnetNegotiationCore/Interpreters/TelnetStandardInterpreter.cs @@ -104,7 +104,16 @@ public TelnetInterpreter(TelnetMode mode, ILogger logger) SupportedCharacterSets = new Lazy(CharacterSets, true); var li = new List, StateMachine>> { - SetupSafeNegotiation, SetupEORNegotiation, SetupMSSPNegotiation, SetupMSDPNegotiation, SetupGMCPNegotiation, SetupTelnetTerminalType, SetupCharsetNegotiation, SetupNAWS, SetupStandardProtocol + SetupSafeNegotiation, + SetupEORNegotiation, + SetupSuppressGANegotiation, + SetupMSSPNegotiation, + SetupMSDPNegotiation, + SetupGMCPNegotiation, + SetupTelnetTerminalType, + SetupCharsetNegotiation, + SetupNAWS, + SetupStandardProtocol }.AggregateRight(TelnetStateMachine, (func, stateMachine) => func(stateMachine)); if (logger.IsEnabled(LogLevel.Trace)) diff --git a/TelnetNegotiationCore/Interpreters/TelnetSuppressGAInterpreter.cs b/TelnetNegotiationCore/Interpreters/TelnetSuppressGAInterpreter.cs new file mode 100644 index 0000000..b085deb --- /dev/null +++ b/TelnetNegotiationCore/Interpreters/TelnetSuppressGAInterpreter.cs @@ -0,0 +1,107 @@ +using Microsoft.Extensions.Logging; +using Stateless; +using System.Threading.Tasks; +using TelnetNegotiationCore.Models; + +namespace TelnetNegotiationCore.Interpreters +{ + public partial class TelnetInterpreter + { + private bool? _doGA = true; + + /// + /// Character set Negotiation will set the Character Set and Character Page Server & Client have agreed to. + /// + /// The state machine. + /// Itself + private StateMachine SetupSuppressGANegotiation(StateMachine tsm) + { + if (Mode == TelnetMode.Server) + { + tsm.Configure(State.Do) + .Permit(Trigger.SUPPRESSGOAHEAD, State.DoSUPPRESSGOAHEAD); + + tsm.Configure(State.Dont) + .Permit(Trigger.SUPPRESSGOAHEAD, State.DontSUPPRESSGOAHEAD); + + tsm.Configure(State.DoSUPPRESSGOAHEAD) + .SubstateOf(State.Accepting) + .OnEntryAsync(OnDoSuppressGAAsync); + + tsm.Configure(State.DontSUPPRESSGOAHEAD) + .SubstateOf(State.Accepting) + .OnEntryAsync(OnDontSuppressGAAsync); + + RegisterInitialWilling(WillingSuppressGAAsync); + } + else + { + tsm.Configure(State.Willing) + .Permit(Trigger.SUPPRESSGOAHEAD, State.WillSUPPRESSGOAHEAD); + + tsm.Configure(State.Refusing) + .Permit(Trigger.SUPPRESSGOAHEAD, State.WontSUPPRESSGOAHEAD); + + tsm.Configure(State.WontSUPPRESSGOAHEAD) + .SubstateOf(State.Accepting) + .OnEntryAsync(WontSuppressGAAsync); + + tsm.Configure(State.WillSUPPRESSGOAHEAD) + .SubstateOf(State.Accepting) + .OnEntryAsync(OnWillSuppressGAAsync); + } + + return tsm; + } + + + private async Task OnSUPPRESSGOAHEADPrompt() + { + _Logger.LogDebug("Connection: {ConnectionState}", "Server is prompting SUPPRESSGOAHEAD"); + await (SignalOnPromptingAsync?.Invoke() ?? Task.CompletedTask); + } + + private async Task OnDontSuppressGAAsync() + { + _Logger.LogDebug("Connection: {ConnectionState}", "Client won't do SUPPRESSGOAHEAD - do nothing"); + _doGA = true; + await Task.CompletedTask; + } + + private async Task WontSuppressGAAsync() + { + _Logger.LogDebug("Connection: {ConnectionState}", "Server won't do SUPPRESSGOAHEAD - do nothing"); + _doGA = true; + await Task.CompletedTask; + } + + /// + /// Announce we do SUPPRESSGOAHEAD negotiation to the client. + /// + private async Task WillingSuppressGAAsync() + { + _Logger.LogDebug("Connection: {ConnectionState}", "Announcing willingness to SUPPRESSGOAHEAD!"); + await CallbackNegotiationAsync([(byte)Trigger.IAC, (byte)Trigger.WILL, (byte)Trigger.SUPPRESSGOAHEAD]); + } + + /// + /// Store that we are now in SUPPRESSGOAHEAD mode. + /// + private Task OnDoSuppressGAAsync(StateMachine.Transition _) + { + _Logger.LogDebug("Connection: {ConnectionState}", "Client supports End of Record."); + _doGA =false; + return Task.CompletedTask; + } + + /// + /// Store that we are now in SUPPRESSGOAHEAD mode. + /// + private async Task OnWillSuppressGAAsync(StateMachine.Transition _) + { + _Logger.LogDebug("Connection: {ConnectionState}", "Server supports End of Record."); + _doGA = false; + await CallbackNegotiationAsync([(byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.SUPPRESSGOAHEAD]); + } + } +} diff --git a/TelnetNegotiationCore/Models/State.cs b/TelnetNegotiationCore/Models/State.cs index 6b00673..5574a57 100644 --- a/TelnetNegotiationCore/Models/State.cs +++ b/TelnetNegotiationCore/Models/State.cs @@ -102,7 +102,11 @@ public enum State : sbyte EvaluatingMSDP, CompletingMSDP, AlmostNegotiatingMSDP, - EscapingMSDP + EscapingMSDP, + DoSUPPRESSGOAHEAD, + DontSUPPRESSGOAHEAD, + WillSUPPRESSGOAHEAD, + WontSUPPRESSGOAHEAD #endregion MSDP Negotiation } } diff --git a/TelnetNegotiationCore/Models/Trigger.cs b/TelnetNegotiationCore/Models/Trigger.cs index 5aef643..8d5e4f4 100644 --- a/TelnetNegotiationCore/Models/Trigger.cs +++ b/TelnetNegotiationCore/Models/Trigger.cs @@ -297,6 +297,13 @@ public enum Trigger : short /// NOP = 241, /// + /// Go ahead. Used, under certain circumstances, to tell the other end that it can transmit. + /// + /// + /// RFC 854: http://www.faqs.org/rfcs/rfc854.html + /// + GA = 249, + /// /// The start of sub-negotiation options. /// ///