Skip to content

Commit 0006726

Browse files
General internal improvements with caching.
1 parent dbf6f76 commit 0006726

14 files changed

+685
-659
lines changed

TelnetNegotiationCore.Functional/MSDPLibrary.fs

+81-66
Original file line numberDiff line numberDiff line change
@@ -5,73 +5,88 @@ open System.Text.Json
55
open System.Text.Json.Nodes
66

77
module MSDPLibrary =
8-
type Trigger =
9-
| NULL = 0uy
10-
| MSDP_VAR = 1uy
11-
| MSDP_VAL = 2uy
12-
| MSDP_TABLE_OPEN = 3uy
13-
| MSDP_TABLE_CLOSE = 4uy
14-
| MSDP_ARRAY_OPEN = 5uy
15-
| MSDP_ARRAY_CLOSE = 6uy
8+
type Trigger =
9+
| NULL = 0uy
10+
| MSDP_VAR = 1uy
11+
| MSDP_VAL = 2uy
12+
| MSDP_TABLE_OPEN = 3uy
13+
| MSDP_TABLE_CLOSE = 4uy
14+
| MSDP_ARRAY_OPEN = 5uy
15+
| MSDP_ARRAY_CLOSE = 6uy
1616

17-
[<TailCall>]
18-
let rec private MSDPScanTailRec (root: obj, array: byte seq, encoding: Encoding) =
19-
let rec scan accRoot accArray =
20-
if Seq.length accArray = 0 then (accRoot, accArray)
21-
else
22-
match accArray |> Seq.head with
23-
| 1uy ->
24-
let key = encoding.GetString(accArray |> Seq.skip(1) |> Seq.takeWhile(fun x -> x <> byte Trigger.MSDP_VAL) |> Array.ofSeq)
25-
let (calculatedValue, leftoverArray) = scan root (accArray |> Seq.skip(1) |> Seq.skipWhile(fun x -> x <> byte Trigger.MSDP_VAL))
26-
scan ((accRoot :?> Map<string, obj>).Add(key, calculatedValue)) leftoverArray
27-
| 2uy ->
28-
if accRoot :? Map<string, obj> then
29-
scan (Map<string, obj> []) (accArray |> Seq.skip(1))
30-
elif accRoot :? List<obj> then
31-
let (calculatedValue, leftoverArray) = scan (Map<string, obj> []) (accArray |> Seq.skip(1))
32-
scan ((accRoot :?> List<obj>) @ [calculatedValue]) leftoverArray
33-
else
34-
scan accRoot (accArray |> Seq.skip(1))
35-
| 3uy ->
36-
scan (Map<string, obj> []) (accArray |> Seq.skip(1))
37-
| 5uy ->
38-
scan (List<obj>.Empty) (accArray |> Seq.skip(1))
39-
| 4uy | 6uy ->
40-
(accRoot, accArray |> Seq.skip(1))
41-
| _ ->
42-
(encoding.GetString(accArray |> Seq.takeWhile(fun x -> x > 6uy) |> Array.ofSeq), accArray |> Seq.skipWhile(fun x -> x > 6uy))
43-
scan root array
17+
let emptyMap: Map<string, obj> = Map<string, obj> []
18+
19+
let MSDPVal: byte = byte Trigger.MSDP_VAL
4420

45-
let public MSDPScan(array: byte seq, encoding) =
46-
let result, _ = MSDPScanTailRec(Map<string,obj> [], array, encoding)
47-
result
21+
[<TailCall>]
22+
let rec private MSDPScanTailRec (root: obj, array: byte seq, encoding: Encoding) =
23+
let rec scanRec accRoot accArray =
24+
if Seq.length accArray = 0 then
25+
(accRoot, accArray)
26+
else
27+
match accArray |> Seq.head with
28+
| 1uy ->
29+
let key =
30+
encoding.GetString(
31+
accArray |> Seq.skip 1 |> Seq.takeWhile (fun x -> x <> MSDPVal) |> Array.ofSeq
32+
)
4833

49-
let parseJsonRoot (jsonRootNode: JsonNode, encoding: Encoding) =
50-
let rec parseJsonValue (jsonNode: JsonNode) =
51-
match jsonNode.GetValueKind() with
52-
| JsonValueKind.Object ->
53-
let parsedObj =
54-
jsonNode.AsObject()
55-
|> Seq.map (fun prop ->
56-
let key = prop.Key
57-
let value = parseJsonValue prop.Value
58-
[byte Trigger.MSDP_VAR] @ (encoding.GetBytes(key) |> List.ofArray) @ [byte Trigger.MSDP_VAL] @ value
59-
) |> List.concat
60-
[byte Trigger.MSDP_TABLE_OPEN] @ parsedObj @ [(byte)Trigger.MSDP_TABLE_CLOSE]
61-
| JsonValueKind.Array ->
62-
let parsedArr =
63-
jsonNode.AsArray()
64-
|> Seq.map (fun prop -> [byte Trigger.MSDP_VAL] @ parseJsonValue(prop))
65-
|> List.ofSeq
66-
|> List.concat
67-
[byte Trigger.MSDP_ARRAY_OPEN] @ parsedArr @ [byte Trigger.MSDP_ARRAY_CLOSE]
68-
| JsonValueKind.String -> encoding.GetBytes(jsonNode.AsValue().ToString()) |> List.ofArray
69-
| JsonValueKind.Number -> encoding.GetBytes(jsonNode.AsValue().ToString()) |> List.ofArray
70-
| JsonValueKind.True -> encoding.GetBytes("1") |> List.ofArray
71-
| JsonValueKind.False -> encoding.GetBytes("0") |> List.ofArray
72-
| JsonValueKind.Null -> encoding.GetBytes("-1") |> List.ofArray
73-
| _ -> failwith "Invalid JSON value"
74-
parseJsonValue jsonRootNode
34+
let cv, rest =
35+
scanRec root (accArray |> Seq.skip 1 |> Seq.skipWhile (fun x -> x <> MSDPVal))
7536

76-
let public Report(jsonString: string, encoding: Encoding) =
77-
parseJsonRoot(JsonValue.Parse(jsonString), encoding) |> Array.ofList
37+
scanRec ((accRoot :?> Map<string, obj>).Add(key, cv)) rest
38+
| 2uy ->
39+
match accRoot with
40+
| :? Map<string, obj> -> scanRec emptyMap (accArray |> Seq.skip 1)
41+
| :? List<obj> ->
42+
let cv, rest = scanRec emptyMap (accArray |> Seq.skip 1)
43+
44+
scanRec ((accRoot :?> List<obj>) @ [ cv ]) rest
45+
| _ -> scanRec accRoot (accArray |> Seq.skip 1)
46+
| 3uy -> scanRec emptyMap (accArray |> Seq.skip 1)
47+
| 4uy -> accRoot, accArray |> Seq.skip 1
48+
| 5uy -> scanRec [] (accArray |> Seq.skip 1)
49+
| 6uy -> accRoot, accArray |> Seq.skip 1
50+
| _ ->
51+
encoding.GetString(accArray |> Seq.takeWhile (fun x -> x > 6uy) |> Array.ofSeq),
52+
accArray |> Seq.skipWhile (fun x -> x > 6uy)
53+
54+
scanRec root array
55+
56+
let public MSDPScan (array: byte seq, encoding) =
57+
let result, _ = MSDPScanTailRec(emptyMap, array, encoding)
58+
result
59+
60+
let parseJsonRoot (jsonRootNode: JsonNode, encoding: Encoding) =
61+
let rec parseJsonValue (jsonNode: JsonNode) =
62+
match jsonNode.GetValueKind() with
63+
| JsonValueKind.Object ->
64+
let parsedObj =
65+
jsonNode.AsObject()
66+
|> Seq.map (fun prop ->
67+
[ byte Trigger.MSDP_VAR ]
68+
@ (encoding.GetBytes(prop.Key) |> List.ofArray)
69+
@ [ byte Trigger.MSDP_VAL ]
70+
@ parseJsonValue prop.Value)
71+
|> List.concat
72+
73+
[ byte Trigger.MSDP_TABLE_OPEN ] @ parsedObj @ [ byte Trigger.MSDP_TABLE_CLOSE ]
74+
| JsonValueKind.Array ->
75+
let parsedArr =
76+
jsonNode.AsArray()
77+
|> Seq.map (fun prop -> [ byte Trigger.MSDP_VAL ] @ parseJsonValue prop)
78+
|> List.ofSeq
79+
|> List.concat
80+
81+
[ byte Trigger.MSDP_ARRAY_OPEN ] @ parsedArr @ [ byte Trigger.MSDP_ARRAY_CLOSE ]
82+
| JsonValueKind.String -> encoding.GetBytes(jsonNode.AsValue().ToString()) |> List.ofArray
83+
| JsonValueKind.Number -> encoding.GetBytes(jsonNode.AsValue().ToString()) |> List.ofArray
84+
| JsonValueKind.True -> encoding.GetBytes("1") |> List.ofArray
85+
| JsonValueKind.False -> encoding.GetBytes("0") |> List.ofArray
86+
| JsonValueKind.Null -> encoding.GetBytes("-1") |> List.ofArray
87+
| _ -> failwith "Invalid JSON value"
88+
89+
parseJsonValue jsonRootNode
90+
91+
let public Report (jsonString: string, encoding: Encoding) =
92+
parseJsonRoot (JsonValue.Parse(jsonString), encoding) |> Array.ofList

TelnetNegotiationCore.TestClient/MockPipelineClient.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ static async ValueTask ReadFromPipeline(TelnetInterpreter telnet, PipeReader rea
8484

8585
foreach(var segment in read.Buffer)
8686
{
87-
await telnet.InterpretByteArrayAsync([..segment.Span]);
87+
await telnet.InterpretByteArrayAsync(segment);
8888
}
8989

9090
// tell the pipe that we used everything

TelnetNegotiationCore.TestServer/KestrelMockServer.cs

+3-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using TelnetNegotiationCore.Models;
88
using Microsoft.AspNetCore.Connections;
99
using System.IO.Pipelines;
10-
using System.Collections.Immutable;
1110
using Microsoft.Extensions.Logging;
1211
using TelnetNegotiationCore.Handlers;
1312

@@ -78,7 +77,7 @@ public override async Task OnConnectedAsync(ConnectionContext connection)
7877
{
7978
_logger.LogInformation("{ConnectionId} connected", connection.ConnectionId);
8079

81-
var MSDPHandler = new MSDPServerHandler(new MSDPServerModel(MSDPUpdateBehavior)
80+
var msdpHandler = new MSDPServerHandler(new MSDPServerModel(MSDPUpdateBehavior)
8281
{
8382
Commands = () => ["help", "stats", "info"],
8483
Configurable_Variables = () => ["CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID"],
@@ -92,7 +91,7 @@ public override async Task OnConnectedAsync(ConnectionContext connection)
9291
SignalOnGMCPAsync = SignalGMCPAsync,
9392
SignalOnMSSPAsync = SignalMSSPAsync,
9493
SignalOnNAWSAsync = SignalNAWSAsync,
95-
SignalOnMSDPAsync = (telnet, config) => SignalMSDPAsync(MSDPHandler, telnet, config),
94+
SignalOnMSDPAsync = (telnet, config) => SignalMSDPAsync(msdpHandler, telnet, config),
9695
CallbackNegotiationAsync = x => WriteToOutputStreamAsync(x, connection.Transport.Output),
9796
CharsetOrder = [Encoding.GetEncoding("utf-8"), Encoding.GetEncoding("iso-8859-1")]
9897
}
@@ -116,7 +115,7 @@ public override async Task OnConnectedAsync(ConnectionContext connection)
116115

117116
foreach (var segment in buffer)
118117
{
119-
await telnet.InterpretByteArrayAsync(segment.Span.ToImmutableArray());
118+
await telnet.InterpretByteArrayAsync(segment);
120119
}
121120

122121
if (result.IsCompleted)

TelnetNegotiationCore/Interpreters/TelnetCharsetInterpreter.cs

+9-9
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,15 @@ private StateMachine<State, Trigger> SetupCharsetNegotiation(StateMachine<State,
8888

8989
tsm.Configure(State.WontDoCharset)
9090
.SubstateOf(State.Accepting)
91-
.OnEntry(() => _Logger.LogDebug("Connection: {ConnectionState}", "Won't do Character Set - do nothing"));
91+
.OnEntry(() => _logger.LogDebug("Connection: {ConnectionState}", "Won't do Character Set - do nothing"));
9292

9393
tsm.Configure(State.DoCharset)
9494
.SubstateOf(State.Accepting)
9595
.OnEntryAsync(async x => await OnDoCharsetAsync(x));
9696

9797
tsm.Configure(State.DontCharset)
9898
.SubstateOf(State.Accepting)
99-
.OnEntry(() => _Logger.LogDebug("Connection: {ConnectionState}", "Client won't do Character Set - do nothing"));
99+
.OnEntry(() => _logger.LogDebug("Connection: {ConnectionState}", "Client won't do Character Set - do nothing"));
100100

101101
tsm.Configure(State.SubNegotiation)
102102
.Permit(Trigger.CHARSET, State.AlmostNegotiatingCharset);
@@ -209,7 +209,7 @@ private async ValueTask CompleteCharsetAsync(StateMachine<State, Trigger>.Transi
209209
var sep = ascii.GetString(_charsetByteState, 0, 1)?[0];
210210
var charsetsOffered = ascii.GetString(_charsetByteState, 1, _charsetByteIndex - 1).Split(sep ?? ' ');
211211

212-
_Logger.LogDebug("Charsets offered to us: {@charsetResultDebug}", charsetsOffered);
212+
_logger.LogDebug("Charsets offered to us: {@charsetResultDebug}", charsetsOffered);
213213

214214
var encodingDict = AllowedEncodings().ToDictionary(x => x.GetEncoding().WebName);
215215
var offeredEncodingInfo = charsetsOffered.Select(x => { try { return encodingDict[Encoding.GetEncoding(x).WebName]; } catch { return null; } }).Where(x => x != null);
@@ -222,7 +222,7 @@ private async ValueTask CompleteCharsetAsync(StateMachine<State, Trigger>.Transi
222222
return;
223223
}
224224

225-
_Logger.LogDebug("Charsets chosen by us: {@charsetWebName} (CP: {@cp})", chosenEncoding.WebName, chosenEncoding.CodePage);
225+
_logger.LogDebug("Charsets chosen by us: {@charsetWebName} (CP: {@cp})", chosenEncoding.WebName, chosenEncoding.CodePage);
226226

227227
byte[] preamble = [(byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.ACCEPTED];
228228
byte[] charsetAscii = ascii.GetBytes(chosenEncoding.WebName);
@@ -249,10 +249,10 @@ private async ValueTask CompleteAcceptedCharsetAsync(StateMachine<State, Trigger
249249
}
250250
catch (Exception ex)
251251
{
252-
_Logger.LogError(ex, "Unexpected error during Accepting Charset Negotiation. Could not find charset: {charset}", ascii.GetString(_acceptedCharsetByteState, 0, _acceptedCharsetByteIndex));
252+
_logger.LogError(ex, "Unexpected error during Accepting Charset Negotiation. Could not find charset: {charset}", ascii.GetString(_acceptedCharsetByteState, 0, _acceptedCharsetByteIndex));
253253
await CallbackNegotiationAsync([(byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.CHARSET, (byte)Trigger.REJECTED, (byte)Trigger.IAC, (byte)Trigger.SE]);
254254
}
255-
_Logger.LogInformation("Connection: Accepted Charset Negotiation for: {charset}", CurrentEncoding.WebName);
255+
_logger.LogInformation("Connection: Accepted Charset Negotiation for: {charset}", CurrentEncoding.WebName);
256256
_charsetOffered = false;
257257
}
258258

@@ -261,7 +261,7 @@ private async ValueTask CompleteAcceptedCharsetAsync(StateMachine<State, Trigger
261261
/// </summary>
262262
private async ValueTask OnWillingCharsetAsync(StateMachine<State, Trigger>.Transition _)
263263
{
264-
_Logger.LogDebug("Connection: {ConnectionState}", "Request charset negotiation from Client");
264+
_logger.LogDebug("Connection: {ConnectionState}", "Request charset negotiation from Client");
265265
await CallbackNegotiationAsync([(byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.CHARSET]);
266266
_charsetOffered = false;
267267
}
@@ -271,7 +271,7 @@ private async ValueTask OnWillingCharsetAsync(StateMachine<State, Trigger>.Trans
271271
/// </summary>
272272
private async ValueTask WillingCharsetAsync()
273273
{
274-
_Logger.LogDebug("Connection: {ConnectionState}", "Announcing willingness to Charset!");
274+
_logger.LogDebug("Connection: {ConnectionState}", "Announcing willingness to Charset!");
275275
await CallbackNegotiationAsync([(byte)Trigger.IAC, (byte)Trigger.WILL, (byte)Trigger.CHARSET]);
276276
}
277277

@@ -280,7 +280,7 @@ private async ValueTask WillingCharsetAsync()
280280
/// </summary>
281281
private async ValueTask OnDoCharsetAsync(StateMachine<State, Trigger>.Transition _)
282282
{
283-
_Logger.LogDebug("Charsets String: {CharsetList}", ";" + string.Join(";", _charsetOrder(AllowedEncodings()).Select(x => x.WebName)));
283+
_logger.LogDebug("Charsets String: {CharsetList}", ";" + string.Join(";", _charsetOrder(AllowedEncodings()).Select(x => x.WebName)));
284284
await CallbackNegotiationAsync(SupportedCharacterSets.Value);
285285
_charsetOffered = true;
286286
}

TelnetNegotiationCore/Interpreters/TelnetEORInterpreter.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,20 @@ private StateMachine<State, Trigger> SetupEORNegotiation(StateMachine<State, Tri
6666

6767
private async ValueTask OnEORPrompt()
6868
{
69-
_Logger.LogDebug("Connection: {ConnectionState}", "Server is prompting EOR");
69+
_logger.LogDebug("Connection: {ConnectionState}", "Server is prompting EOR");
7070
await (SignalOnPromptingAsync?.Invoke() ?? ValueTask.CompletedTask);
7171
}
7272

7373
private ValueTask OnDontEORAsync()
7474
{
75-
_Logger.LogDebug("Connection: {ConnectionState}", "Client won't do EOR - do nothing");
75+
_logger.LogDebug("Connection: {ConnectionState}", "Client won't do EOR - do nothing");
7676
_doEOR = false;
7777
return ValueTask.CompletedTask;
7878
}
7979

8080
private ValueTask WontEORAsync()
8181
{
82-
_Logger.LogDebug("Connection: {ConnectionState}", "Server won't do EOR - do nothing");
82+
_logger.LogDebug("Connection: {ConnectionState}", "Server won't do EOR - do nothing");
8383
_doEOR = false;
8484
return ValueTask.CompletedTask;
8585
}
@@ -89,7 +89,7 @@ private ValueTask WontEORAsync()
8989
/// </summary>
9090
private async ValueTask WillingEORAsync()
9191
{
92-
_Logger.LogDebug("Connection: {ConnectionState}", "Announcing willingness to EOR!");
92+
_logger.LogDebug("Connection: {ConnectionState}", "Announcing willingness to EOR!");
9393
await CallbackNegotiationAsync([(byte)Trigger.IAC, (byte)Trigger.WILL, (byte)Trigger.TELOPT_EOR]);
9494
}
9595

@@ -98,7 +98,7 @@ private async ValueTask WillingEORAsync()
9898
/// </summary>
9999
private ValueTask OnDoEORAsync(StateMachine<State, Trigger>.Transition _)
100100
{
101-
_Logger.LogDebug("Connection: {ConnectionState}", "Client supports End of Record.");
101+
_logger.LogDebug("Connection: {ConnectionState}", "Client supports End of Record.");
102102
_doEOR = true;
103103
return ValueTask.CompletedTask;
104104
}
@@ -108,7 +108,7 @@ private ValueTask OnDoEORAsync(StateMachine<State, Trigger>.Transition _)
108108
/// </summary>
109109
private async ValueTask OnWillEORAsync(StateMachine<State, Trigger>.Transition _)
110110
{
111-
_Logger.LogDebug("Connection: {ConnectionState}", "Server supports End of Record.");
111+
_logger.LogDebug("Connection: {ConnectionState}", "Server supports End of Record.");
112112
_doEOR = true;
113113
await CallbackNegotiationAsync([(byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.TELOPT_EOR]);
114114
}

TelnetNegotiationCore/Interpreters/TelnetGMCPInterpreter.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ private StateMachine<State, Trigger> SetupGMCPNegotiation(StateMachine<State, Tr
2828

2929
tsm.Configure(State.DoGMCP)
3030
.SubstateOf(State.Accepting)
31-
.OnEntry(() => _Logger.LogDebug("Connection: {ConnectionState}", "Client will do GMCP"));
31+
.OnEntry(() => _logger.LogDebug("Connection: {ConnectionState}", "Client will do GMCP"));
3232

3333
tsm.Configure(State.DontGMCP)
3434
.SubstateOf(State.Accepting)
35-
.OnEntry(() => _Logger.LogDebug("Connection: {ConnectionState}", "Client will not GMCP"));
35+
.OnEntry(() => _logger.LogDebug("Connection: {ConnectionState}", "Client will not GMCP"));
3636

3737
RegisterInitialWilling(async () => await WillGMCPAsync(null));
3838
}
@@ -50,7 +50,7 @@ private StateMachine<State, Trigger> SetupGMCPNegotiation(StateMachine<State, Tr
5050

5151
tsm.Configure(State.WontGMCP)
5252
.SubstateOf(State.Accepting)
53-
.OnEntry(() => _Logger.LogDebug("Connection: {ConnectionState}", "Client will GMCP"));
53+
.OnEntry(() => _logger.LogDebug("Connection: {ConnectionState}", "Client will GMCP"));
5454
}
5555

5656
tsm.Configure(State.SubNegotiation)
@@ -142,14 +142,14 @@ private async ValueTask CompleteGMCPNegotiation(StateMachine<State, Trigger>.Tra
142142
/// <returns>ValueTask</returns>
143143
private async ValueTask WillGMCPAsync(StateMachine<State, Trigger>.Transition _)
144144
{
145-
_Logger.LogDebug("Connection: {ConnectionState}", "Announcing the server will GMCP");
145+
_logger.LogDebug("Connection: {ConnectionState}", "Announcing the server will GMCP");
146146

147147
await CallbackNegotiationAsync([(byte)Trigger.IAC, (byte)Trigger.WILL, (byte)Trigger.GMCP]);
148148
}
149149

150150
private async ValueTask DoGMCPAsync(StateMachine<State, Trigger>.Transition _)
151151
{
152-
_Logger.LogDebug("Connection: {ConnectionState}", "Announcing the client can do GMCP");
152+
_logger.LogDebug("Connection: {ConnectionState}", "Announcing the client can do GMCP");
153153

154154
await CallbackNegotiationAsync([(byte)Trigger.IAC, (byte)Trigger.DO, (byte)Trigger.GMCP]);
155155
}

0 commit comments

Comments
 (0)