Skip to content

Commit e9163e6

Browse files
CopilotCopilotHarryCordewener
authored
Reduce allocations: cache static byte arrays, pre-size output buffers, revert MSSP over-optimization (#39)
* Initial plan * Cache repeated byte[] allocations as static readonly fields in GMCPProtocol and MSDPProtocol Add private static readonly byte[] fields for commonly repeated IAC/WILL/DO negotiation sequences to avoid allocating new arrays on every call: - GMCPProtocol: s_willGmcp, s_doGmcp - MSDPProtocol: s_willMsdp, s_doMsdp Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Cache repeated byte[] allocations as private static readonly fields Replace inline 'new byte[]' allocations with cached static readonly fields in 6 protocol files to avoid repeated heap allocations of identical arrays: - MSSPProtocol.cs: s_willMssp, s_doMssp - EORProtocol.cs: s_willEor, s_doEor - NAWSProtocol.cs: s_wontNaws, s_doNaws - SuppressGoAheadProtocol.cs: s_willSga, s_doSga - EchoProtocol.cs: s_willEcho, s_doEcho - TerminalTypeProtocol.cs: s_willTtype, s_doTtype Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Cache repeated byte[] allocations as static readonly fields in protocol files Replace inline new byte[] allocations with private static readonly byte[] fields in CharsetProtocol, MCCPProtocol, TerminalSpeedProtocol, XDisplayProtocol, FlowControlProtocol, NewEnvironProtocol, and EnvironProtocol. This avoids repeated heap allocations for constant telnet negotiation sequences. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Revert MSSPReadConfig/ConvertToMSSP refactoring: collection expression pattern is fine for MSSP data sizes Co-authored-by: HarryCordewener <5649138+HarryCordewener@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Harry Cordewener <admin@twilightdays.org> Co-authored-by: HarryCordewener <5649138+HarryCordewener@users.noreply.github.com>
1 parent cad5897 commit e9163e6

19 files changed

Lines changed: 164 additions & 72 deletions

TelnetNegotiationCore/Interpreters/TelnetEORInterpreter.cs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ public partial class TelnetInterpreter
1010
{
1111
private bool? _doEOR = null;
1212

13+
// Cached negotiation byte arrays to avoid repeated allocations
14+
private static readonly byte[] s_willEOR = [(byte)Trigger.IAC, (byte)Trigger.WILL, (byte)Trigger.TELOPT_EOR];
15+
private static readonly byte[] s_doEOR = [(byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.TELOPT_EOR];
16+
1317
/// <summary>
1418
/// Character set Negotiation will set the Character Set and Character Page Server & Client have agreed to.
1519
/// </summary>
@@ -94,7 +98,7 @@ private ValueTask WontEORAsync()
9498
private async ValueTask WillingEORAsync()
9599
{
96100
_logger.LogDebug("Connection: {ConnectionState}", "Announcing willingness to EOR!");
97-
await WriteToNetworkAsync((byte[])[(byte)Trigger.IAC, (byte)Trigger.WILL, (byte)Trigger.TELOPT_EOR]);
101+
await WriteToNetworkAsync(s_willEOR);
98102
}
99103

100104
/// <summary>
@@ -114,7 +118,7 @@ private async ValueTask OnWillEORAsync(StateMachine<State, Trigger>.Transition _
114118
{
115119
_logger.LogDebug("Connection: {ConnectionState}", "Server supports End of Record.");
116120
_doEOR = true;
117-
await WriteToNetworkAsync((byte[])[(byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.TELOPT_EOR]);
121+
await WriteToNetworkAsync(s_doEOR);
118122
}
119123

120124
/// <summary>
@@ -128,15 +132,30 @@ public async ValueTask SendPromptAsync(byte[] send)
128132
var safeSend = TelnetSafeBytesInternal(send);
129133
if (_doEOR is null or false)
130134
{
131-
await WriteToNetworkAsync((byte[])[.. safeSend, (byte)'\r', (byte)'\n']);
135+
// Pre-allocate exact-size buffer: safeSend + CR LF
136+
var output = new byte[safeSend.Length + 2];
137+
safeSend.AsSpan().CopyTo(output);
138+
output[safeSend.Length] = (byte)'\r';
139+
output[safeSend.Length + 1] = (byte)'\n';
140+
await WriteToNetworkAsync(output);
132141
}
133142
else if(_doEOR is true)
134143
{
135-
await WriteToNetworkAsync((byte[])[.. safeSend, (byte)Trigger.IAC, (byte)Trigger.EOR]);
144+
// Pre-allocate exact-size buffer: safeSend + IAC EOR
145+
var output = new byte[safeSend.Length + 2];
146+
safeSend.AsSpan().CopyTo(output);
147+
output[safeSend.Length] = (byte)Trigger.IAC;
148+
output[safeSend.Length + 1] = (byte)Trigger.EOR;
149+
await WriteToNetworkAsync(output);
136150
}
137151
else if (_doGA is not null)
138152
{
139-
await WriteToNetworkAsync((byte[])[.. safeSend, (byte)Trigger.IAC, (byte)Trigger.GA]);
153+
// Pre-allocate exact-size buffer: safeSend + IAC GA
154+
var output = new byte[safeSend.Length + 2];
155+
safeSend.AsSpan().CopyTo(output);
156+
output[safeSend.Length] = (byte)Trigger.IAC;
157+
output[safeSend.Length + 1] = (byte)Trigger.GA;
158+
await WriteToNetworkAsync(output);
140159
}
141160
}
142161

@@ -148,6 +167,12 @@ public async ValueTask SendPromptAsync(byte[] send)
148167
/// <returns>A completed ValueTask</returns>
149168
public async ValueTask SendAsync(byte[] send)
150169
{
151-
await WriteToNetworkAsync((byte[])[.. TelnetSafeBytesInternal(send), (byte)'\r', (byte)'\n']);
170+
var safeSend = TelnetSafeBytesInternal(send);
171+
// Pre-allocate exact-size buffer: safeSend + CR LF
172+
var output = new byte[safeSend.Length + 2];
173+
safeSend.AsSpan().CopyTo(output);
174+
output[safeSend.Length] = (byte)'\r';
175+
output[safeSend.Length + 1] = (byte)'\n';
176+
await WriteToNetworkAsync(output);
152177
}
153178
}

TelnetNegotiationCore/Interpreters/TelnetGMCPInterpreter.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,17 @@ public ValueTask SendGMCPCommand(string package, byte[] command) =>
6060
/// <returns>A ValueTask representing the asynchronous operation.</returns>
6161
public async ValueTask SendGMCPCommand(byte[] package, byte[] command)
6262
{
63-
await WriteToNetworkAsync(
64-
(byte[])[
65-
(byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.GMCP,
66-
.. package,
67-
.. CurrentEncoding.GetBytes(" "),
68-
.. command,
69-
.. new[] { (byte)Trigger.IAC, (byte)Trigger.SE },
70-
]);
63+
// Pre-allocate exact-size buffer: IAC SB GMCP <package> ' ' <command> IAC SE
64+
var output = new byte[3 + package.Length + 1 + command.Length + 2];
65+
output[0] = (byte)Trigger.IAC;
66+
output[1] = (byte)Trigger.SB;
67+
output[2] = (byte)Trigger.GMCP;
68+
package.AsSpan().CopyTo(output.AsSpan(3));
69+
output[3 + package.Length] = (byte)' ';
70+
command.AsSpan().CopyTo(output.AsSpan(3 + package.Length + 1));
71+
output[output.Length - 2] = (byte)Trigger.IAC;
72+
output[output.Length - 1] = (byte)Trigger.SE;
73+
await WriteToNetworkAsync(output);
7174
}
7275

7376
/// <summary>

TelnetNegotiationCore/Interpreters/TelnetNAWSInterpreter.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ public partial class TelnetInterpreter
4949
/// </summary>
5050
private bool _WillingToDoNAWS = false;
5151

52+
// Cached negotiation byte array to avoid repeated allocations
53+
private static readonly byte[] s_doNAWS = [(byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.NAWS];
54+
5255
public async ValueTask SendNAWS(short width, short height)
5356
{
5457
if(!_WillingToDoNAWS) await default(ValueTask);
@@ -88,7 +91,7 @@ public async ValueTask RequestNAWSAsync(StateMachine<State, Trigger>.Transition?
8891
{
8992
_logger.LogDebug("Connection: {ConnectionState}", "Requesting NAWS details from Client");
9093

91-
await WriteToNetworkAsync((byte[])[(byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.NAWS]);
94+
await WriteToNetworkAsync(s_doNAWS);
9295
_WillingToDoNAWS = true;
9396
}
9497
}

TelnetNegotiationCore/Interpreters/TelnetStandardInterpreter.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -387,11 +387,12 @@ public async ValueTask InterpretAsync(byte bt)
387387
/// <returns>ValueTask</returns>
388388
public async ValueTask InterpretByteArrayAsync(ReadOnlyMemory<byte> byteArray)
389389
{
390-
// Convert to array first to avoid Span across await boundary
391-
var bytes = byteArray.ToArray();
392-
foreach (var b in bytes)
390+
// Index into ReadOnlyMemory<byte> directly to avoid a .ToArray() allocation.
391+
// ReadOnlyMemory<byte> (unlike ReadOnlySpan<byte>) is not a ref struct,
392+
// so it is safe to hold across await boundaries.
393+
for (int i = 0; i < byteArray.Length; i++)
393394
{
394-
await _byteChannel.Writer.WriteAsync(b);
395+
await _byteChannel.Writer.WriteAsync(byteArray.Span[i]);
395396
}
396397
}
397398

TelnetNegotiationCore/Interpreters/TelnetTerminalTypeInterpreter.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,18 @@ public partial class TelnetInterpreter
6767
{ 1024, "MSLP" }
6868
};
6969

70+
// Cached negotiation byte array to avoid repeated allocations
71+
private static readonly byte[] s_requestTerminalType = [
72+
(byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.TTYPE, (byte)Trigger.SEND, (byte)Trigger.IAC,
73+
(byte)Trigger.SE
74+
];
75+
7076
/// <summary>
7177
/// Request Terminal Type from Client. This flips to the next one.
7278
/// </summary>
7379
public async ValueTask RequestTerminalTypeAsync()
7480
{
7581
_logger.LogDebug("Connection: {ConnectionState}", "Telling the client, to send the next Terminal Type.");
76-
await WriteToNetworkAsync((byte[])[
77-
(byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.TTYPE, (byte)Trigger.SEND, (byte)Trigger.IAC,
78-
(byte)Trigger.SE
79-
]);
82+
await WriteToNetworkAsync(s_requestTerminalType);
8083
}
8184
}

TelnetNegotiationCore/Protocols/CharsetProtocol.cs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ namespace TelnetNegotiationCore.Protocols;
2222
/// </remarks>
2323
public class CharsetProtocol : TelnetProtocolPluginBase
2424
{
25+
private static readonly byte[] s_charsetRejected = new byte[] { (byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.REJECTED, (byte)Trigger.IAC, (byte)Trigger.SE };
26+
private static readonly byte[] s_doCharset = new byte[] { (byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.CHARSET };
27+
private static readonly byte[] s_willCharset = new byte[] { (byte)Trigger.IAC, (byte)Trigger.WILL, (byte)Trigger.CHARSET };
28+
private static readonly byte[] s_ttableRejected = new byte[] { (byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.TTABLE_REJECTED, (byte)Trigger.IAC, (byte)Trigger.SE };
29+
private static readonly byte[] s_ttableAck = new byte[] { (byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.TTABLE_ACK, (byte)Trigger.IAC, (byte)Trigger.SE };
30+
private static readonly byte[] s_ttableNak = new byte[] { (byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.TTABLE_NAK, (byte)Trigger.IAC, (byte)Trigger.SE };
31+
2532
private byte[] _charsetByteState = [];
2633
private int _charsetByteIndex = 0;
2734
private byte[] _acceptedCharsetByteState = [];
@@ -327,7 +334,7 @@ private async ValueTask CompleteCharsetAsync(StateMachine<State, Trigger>.Transi
327334

328335
if (_charsetOffered && context.Mode == Interpreters.TelnetInterpreter.TelnetMode.Server)
329336
{
330-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.REJECTED, (byte)Trigger.IAC, (byte)Trigger.SE });
337+
await context.SendNegotiationAsync(s_charsetRejected);
331338
return;
332339
}
333340

@@ -346,7 +353,7 @@ private async ValueTask CompleteCharsetAsync(StateMachine<State, Trigger>.Transi
346353

347354
if (chosenEncoding == null)
348355
{
349-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.REJECTED, (byte)Trigger.IAC, (byte)Trigger.SE });
356+
await context.SendNegotiationAsync(s_charsetRejected);
350357
return;
351358
}
352359

@@ -380,7 +387,7 @@ private async ValueTask CompleteAcceptedCharsetAsync(StateMachine<State, Trigger
380387
catch (Exception ex)
381388
{
382389
context.Logger.LogError(ex, "Unexpected error during Accepting Charset Negotiation. Could not find charset: {charset}", ascii.GetString(_acceptedCharsetByteState!, 0, _acceptedCharsetByteIndex));
383-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.REJECTED, (byte)Trigger.IAC, (byte)Trigger.SE });
390+
await context.SendNegotiationAsync(s_charsetRejected);
384391
}
385392
context.Logger.LogInformation("Connection: Accepted Charset Negotiation for: {charset}", CurrentEncoding.WebName);
386393
_charsetOffered = false;
@@ -389,14 +396,14 @@ private async ValueTask CompleteAcceptedCharsetAsync(StateMachine<State, Trigger
389396
private async ValueTask OnWillingCharsetAsync(StateMachine<State, Trigger>.Transition _, IProtocolContext context)
390397
{
391398
context.Logger.LogDebug("Connection: {ConnectionState}", "Request charset negotiation from Client");
392-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.CHARSET });
399+
await context.SendNegotiationAsync(s_doCharset);
393400
_charsetOffered = false;
394401
}
395402

396403
private async ValueTask WillingCharsetAsync(IProtocolContext context)
397404
{
398405
context.Logger.LogDebug("Connection: {ConnectionState}", "Announcing willingness to Charset!");
399-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.WILL, (byte)Trigger.CHARSET });
406+
await context.SendNegotiationAsync(s_willCharset);
400407
}
401408

402409
private async ValueTask OnDoCharsetAsync(StateMachine<State, Trigger>.Transition _, IProtocolContext context)
@@ -455,15 +462,15 @@ private async ValueTask CompleteTTableAsync(StateMachine<State, Trigger>.Transit
455462
if (_ttableByteIndex < TTABLE_MIN_LENGTH)
456463
{
457464
context.Logger.LogWarning("TTABLE-IS message too short");
458-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.TTABLE_REJECTED, (byte)Trigger.IAC, (byte)Trigger.SE });
465+
await context.SendNegotiationAsync(s_ttableRejected);
459466
return;
460467
}
461468

462469
var version = _ttableByteState[0];
463470
if (version != TTABLE_VERSION_1)
464471
{
465472
context.Logger.LogWarning("Unsupported TTABLE version: {Version}", version);
466-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.TTABLE_REJECTED, (byte)Trigger.IAC, (byte)Trigger.SE });
473+
await context.SendNegotiationAsync(s_ttableRejected);
467474
return;
468475
}
469476

@@ -482,26 +489,26 @@ private async ValueTask CompleteTTableAsync(StateMachine<State, Trigger>.Transit
482489

483490
// Send TTABLE-ACK
484491
context.Logger.LogInformation("TTABLE accepted and acknowledged");
485-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.TTABLE_ACK, (byte)Trigger.IAC, (byte)Trigger.SE });
492+
await context.SendNegotiationAsync(s_ttableAck);
486493
}
487494
else
488495
{
489496
// Send TTABLE-NAK to request retransmission
490497
context.Logger.LogInformation("TTABLE rejected by callback, sending NAK");
491-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.TTABLE_NAK, (byte)Trigger.IAC, (byte)Trigger.SE });
498+
await context.SendNegotiationAsync(s_ttableNak);
492499
}
493500
}
494501
else
495502
{
496503
// No callback registered, reject TTABLE
497504
context.Logger.LogDebug("No TTABLE callback registered, rejecting");
498-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.TTABLE_REJECTED, (byte)Trigger.IAC, (byte)Trigger.SE });
505+
await context.SendNegotiationAsync(s_ttableRejected);
499506
}
500507
}
501508
catch (Exception ex)
502509
{
503510
context.Logger.LogError(ex, "Error processing TTABLE-IS message");
504-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.TTABLE_REJECTED, (byte)Trigger.IAC, (byte)Trigger.SE });
511+
await context.SendNegotiationAsync(s_ttableRejected);
505512
}
506513
}
507514

@@ -614,7 +621,7 @@ public async ValueTask SendTTableRejectedAsync()
614621
throw new InvalidOperationException("Protocol not initialized");
615622
}
616623

617-
await Context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.TTABLE_REJECTED, (byte)Trigger.IAC, (byte)Trigger.SE });
624+
await Context.SendNegotiationAsync(s_ttableRejected);
618625
Context.Logger.LogInformation("Sent TTABLE-REJECTED message");
619626
}
620627

TelnetNegotiationCore/Protocols/EORProtocol.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ namespace TelnetNegotiationCore.Protocols;
2020
[RequiredMethod("OnPrompt", Description = "Configure the callback to handle prompt events (optional but recommended)")]
2121
public class EORProtocol : TelnetProtocolPluginBase
2222
{
23+
private static readonly byte[] s_willEor = new byte[] { (byte)Trigger.IAC, (byte)Trigger.WILL, (byte)Trigger.TELOPT_EOR };
24+
private static readonly byte[] s_doEor = new byte[] { (byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.TELOPT_EOR };
25+
2326
private bool? _doEOR = null;
2427

2528
private Func<ValueTask>? _onPromptReceived;
@@ -218,7 +221,7 @@ private ValueTask WontEORAsync(IProtocolContext context)
218221
private async ValueTask WillingEORAsync(IProtocolContext context)
219222
{
220223
context.Logger.LogDebug("Announcing willingness to EOR!");
221-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.WILL, (byte)Trigger.TELOPT_EOR });
224+
await context.SendNegotiationAsync(s_willEor);
222225
}
223226

224227
private ValueTask OnDoEORAsync(StateMachine<State, Trigger>.Transition _, IProtocolContext context)
@@ -232,7 +235,7 @@ private async ValueTask OnWillEORAsync(StateMachine<State, Trigger>.Transition _
232235
{
233236
context.Logger.LogDebug("Server supports End of Record.");
234237
_doEOR = true;
235-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.TELOPT_EOR });
238+
await context.SendNegotiationAsync(s_doEor);
236239
}
237240

238241
#endregion

TelnetNegotiationCore/Protocols/EchoProtocol.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ namespace TelnetNegotiationCore.Protocols;
2121
[RequiredMethod("OnEchoStateChanged", Description = "Configure the callback to handle echo state changes (optional but recommended)")]
2222
public class EchoProtocol : TelnetProtocolPluginBase
2323
{
24+
private static readonly byte[] s_willEcho = new byte[] { (byte)Trigger.IAC, (byte)Trigger.WILL, (byte)Trigger.ECHO };
25+
private static readonly byte[] s_doEcho = new byte[] { (byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.ECHO };
26+
2427
private bool? _willEcho = null;
2528

2629
private Func<bool, ValueTask>? _onEchoStateChanged;
@@ -242,7 +245,7 @@ private async ValueTask WontEchoAsync(IProtocolContext context)
242245
private async ValueTask WillingEchoAsync(IProtocolContext context)
243246
{
244247
context.Logger.LogDebug("Announcing willingness to ECHO!");
245-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.WILL, (byte)Trigger.ECHO });
248+
await context.SendNegotiationAsync(s_willEcho);
246249
}
247250

248251
private async ValueTask OnDoEchoAsync(StateMachine<State, Trigger>.Transition _, IProtocolContext context)
@@ -260,7 +263,7 @@ private async ValueTask OnWillEchoAsync(StateMachine<State, Trigger>.Transition
260263
context.Logger.LogDebug("Server will echo - client accepting");
261264
var previousState = _willEcho;
262265
_willEcho = true;
263-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.ECHO });
266+
await context.SendNegotiationAsync(s_doEcho);
264267

265268
if (previousState != _willEcho && _onEchoStateChanged != null)
266269
await _onEchoStateChanged(true);

TelnetNegotiationCore/Protocols/EnvironProtocol.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ namespace TelnetNegotiationCore.Protocols;
2626
[RequiredMethod("OnEnvironmentVariables", Description = "Configure the callback to handle environment variable updates (optional but recommended)")]
2727
public class EnvironProtocol : TelnetProtocolPluginBase
2828
{
29+
private static readonly byte[] s_willEnviron = new byte[] { (byte)Trigger.IAC, (byte)Trigger.WILL, (byte)Trigger.ENVIRON };
30+
private static readonly byte[] s_doEnviron = new byte[] { (byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.ENVIRON };
31+
2932
private readonly List<byte> _currentVar = [];
3033
private readonly List<byte> _currentValue = [];
3134
private readonly Dictionary<string, string> _environmentVariables = new();
@@ -344,7 +347,7 @@ private void SaveCurrentVariable()
344347
private async ValueTask WillingEnvironAsync(IProtocolContext context)
345348
{
346349
context.Logger.LogDebug("Announcing willingness to ENVIRON!");
347-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.WILL, (byte)Trigger.ENVIRON });
350+
await context.SendNegotiationAsync(s_willEnviron);
348351
}
349352

350353
private async ValueTask OnDoEnvironAsync(StateMachine<State, Trigger>.Transition _, IProtocolContext context)
@@ -366,7 +369,7 @@ await context.SendNegotiationAsync(new byte[]
366369
private async ValueTask OnWillEnvironAsync(StateMachine<State, Trigger>.Transition _, IProtocolContext context)
367370
{
368371
context.Logger.LogDebug("Server will do ENVIRON");
369-
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.ENVIRON });
372+
await context.SendNegotiationAsync(s_doEnviron);
370373
}
371374

372375
private async ValueTask CompleteEnvironAsync(StateMachine<State, Trigger>.Transition _, IProtocolContext context)

0 commit comments

Comments
 (0)