Skip to content

Commit b747c61

Browse files
Replace netstandard2.1 target with netstandard2.0 (#37)
* Initial plan * Replace netstandard2.1 with netstandard2.0, add polyfills for missing APIs Co-authored-by: HarryCordewener <5649138+HarryCordewener@users.noreply.github.com> * Fix .gitignore and add polyfill limitation documentation 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: HarryCordewener <5649138+HarryCordewener@users.noreply.github.com>
1 parent b109c53 commit b747c61

11 files changed

Lines changed: 86 additions & 23 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,4 +340,5 @@ ASALocalRun/
340340
.localhistory/
341341

342342
# BeatPulse healthcheck temp database
343-
healthchecksdb
343+
healthchecksdb
344+
TestResults/

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ All notable changes to this project will be documented in this file.
1111
- `UsePipe(IDuplexPipe)` — wires `OnNegotiation` to `pipe.Output`; leaves read loop to the caller
1212
- `UseStream(Stream)` — wires `OnNegotiation` via `PipeWriter.Create(stream)`; leaves read loop to the caller
1313
- `ReadFromPipeAsync(TelnetInterpreter, PipeReader, CancellationToken)` — static helper that drives the standard read loop
14-
- Added `System.IO.Pipelines` as an explicit package reference for `net8.0` and `netstandard2.1` targets
14+
- Added `System.IO.Pipelines` as an explicit package reference for `net8.0` and `netstandard2.0` targets
1515

1616
## [2.3.0] - 2026-02-13
1717

TelnetNegotiationCore.Functional/TelnetNegotiationCore.Functional.fsproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>netstandard2.1;net8.0;net10.0</TargetFrameworks>
4+
<TargetFrameworks>netstandard2.0;net8.0;net10.0</TargetFrameworks>
55
<GenerateDocumentationFile>true</GenerateDocumentationFile>
66
</PropertyGroup>
77

88
<ItemGroup>
99
<Compile Include="MSDPLibrary.fs" />
1010
</ItemGroup>
1111

12-
<!-- System.Text.Json for netstandard2.1 -->
13-
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
12+
<!-- System.Text.Json for netstandard2.0 -->
13+
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
1414
<PackageReference Include="System.Text.Json" Version="10.0.2" />
1515
</ItemGroup>
1616

TelnetNegotiationCore/Handlers/MSDPServerHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ await ExecuteOnAsync(item, (var) =>
120120

121121
private async ValueTask ExecuteOnAsync(string item, Func<string, ValueTask> function)
122122
{
123-
var items = item.StartsWith('[')
123+
var items = item.StartsWith("[", StringComparison.Ordinal)
124124
? JsonSerializer.Deserialize<string[]>(item)
125125
: [item];
126126

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#if NETSTANDARD2_0
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace TelnetNegotiationCore;
6+
7+
/// <summary>
8+
/// Polyfills for APIs available in netstandard2.1+ but missing in netstandard2.0.
9+
/// </summary>
10+
internal static class NetStandard20Polyfills
11+
{
12+
/// <summary>
13+
/// Polyfill for <see cref="HashSet{T}.TryGetValue"/> (added in netstandard2.1).
14+
/// Note: Returns the input value rather than the stored value. This is correct for
15+
/// default equality comparers but may differ from the real API with custom comparers.
16+
/// </summary>
17+
public static bool TryGetValue<T>(this HashSet<T> set, T equalValue, out T actualValue)
18+
{
19+
if (set.Contains(equalValue))
20+
{
21+
actualValue = equalValue;
22+
return true;
23+
}
24+
actualValue = default!;
25+
return false;
26+
}
27+
28+
/// <summary>
29+
/// Polyfill for <see cref="Dictionary{TKey,TValue}.TryAdd"/> (added in netstandard2.1).
30+
/// </summary>
31+
public static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue value)
32+
{
33+
if (!dictionary.ContainsKey(key))
34+
{
35+
dictionary.Add(key, value);
36+
return true;
37+
}
38+
return false;
39+
}
40+
41+
/// <summary>
42+
/// Polyfill for <see cref="Enumerable.ToHashSet{T}(IEnumerable{T})"/> (added in netstandard2.1).
43+
/// </summary>
44+
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
45+
{
46+
return new HashSet<T>(source);
47+
}
48+
49+
/// <summary>
50+
/// Polyfill for KeyValuePair deconstruction (added in netstandard2.1).
51+
/// </summary>
52+
public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> kvp, out TKey key, out TValue value)
53+
{
54+
key = kvp.Key;
55+
value = kvp.Value;
56+
}
57+
}
58+
#endif

TelnetNegotiationCore/Interpreters/TelnetMSSPInterpreter.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,11 @@ private void StoreClientMSSPDetails(string variable, IEnumerable<string> value)
8181
{
8282
dynamic valueToSet = value.Count() > 1 ? value : value.First();
8383

84-
if (!_msspConfig().Extended.TryAdd(variable, valueToSet))
84+
if (!_msspConfig().Extended.ContainsKey(variable))
85+
{
86+
_msspConfig().Extended.Add(variable, valueToSet);
87+
}
88+
else
8589
{
8690
_msspConfig().Extended[variable] = valueToSet;
8791
}

TelnetNegotiationCore/Interpreters/TelnetNAWSInterpreter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ private async ValueTask CompleteNAWSAsync(StateMachine<State, Trigger>.Transitio
104104
Array.Reverse(height);
105105
}
106106

107-
ClientWidth = BitConverter.ToInt16(width);
108-
ClientHeight = BitConverter.ToInt16(height);
107+
ClientWidth = BitConverter.ToInt16(width, 0);
108+
ClientHeight = BitConverter.ToInt16(height, 0);
109109

110110
_logger.LogDebug("Negotiated for: {clientWidth} width and {clientHeight} height", ClientWidth, ClientHeight);
111111

TelnetNegotiationCore/Interpreters/TelnetSafeInterpreter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ private static byte[] TelnetSafeBytesInternal(ReadOnlySpan<byte> input)
9595

9696
return result;
9797
#else
98-
// Fallback for netstandard2.1 and .NET 8: ArrayPool worst-case buffer + IndexOf loop
98+
// Fallback for netstandard2.0 and .NET 8: ArrayPool worst-case buffer + IndexOf loop
9999
// with CopyTo block copies. MemoryExtensions.Split<T>(T value) returning
100100
// SpanSplitEnumerator<T> was introduced in .NET 9 and has no available polyfill.
101101
var pooled = ArrayPool<byte>.Shared.Rent(input.Length * 2);
@@ -123,7 +123,7 @@ private static byte[] TelnetSafeBytesInternal(ReadOnlySpan<byte> input)
123123
remaining = remaining[(iacPos + 1)..];
124124
}
125125

126-
return pooled[..writePos].ToArray();
126+
return pooled.AsSpan(0, writePos).ToArray();
127127
}
128128
finally
129129
{

TelnetNegotiationCore/Protocols/MCCPProtocol.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
using TelnetNegotiationCore.Attributes;
99
using TelnetNegotiationCore.Models;
1010
using TelnetNegotiationCore.Plugins;
11-
#if NETSTANDARD2_1
11+
#if NETSTANDARD2_0
1212
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
1313
#endif
1414

@@ -36,7 +36,7 @@ public class MCCPProtocol : TelnetProtocolPluginBase
3636
{
3737
private bool _mccp2Enabled = false;
3838
private bool _mccp3Enabled = false;
39-
#if NETSTANDARD2_1
39+
#if NETSTANDARD2_0
4040
private Stream? _compressionStream;
4141
#else
4242
private ZLibStream? _compressionStream;
@@ -242,8 +242,8 @@ public byte[] DecompressData(byte[] data)
242242

243243
try
244244
{
245-
#if NETSTANDARD2_1
246-
// Use SharpZipLib for .NET Standard 2.1
245+
#if NETSTANDARD2_0
246+
// Use SharpZipLib for .NET Standard 2.0
247247
using var compressedStream = new MemoryStream(data);
248248
using var zlibStream = new InflaterInputStream(compressedStream);
249249
using var outputStream = new MemoryStream();
@@ -371,7 +371,7 @@ private async ValueTask OnWillMCCP3Async(IProtocolContext context)
371371
await context.SendNegotiationAsync(new byte[] { (byte)Trigger.IAC, (byte)Trigger.SB, (byte)Trigger.MCCP3, (byte)Trigger.IAC, (byte)Trigger.SE });
372372
// Start compression
373373
_compressionBuffer = new MemoryStream();
374-
#if NETSTANDARD2_1
374+
#if NETSTANDARD2_0
375375
_compressionStream = new DeflaterOutputStream(_compressionBuffer);
376376
#else
377377
_compressionStream = new ZLibStream(_compressionBuffer, CompressionMode.Compress);
@@ -396,7 +396,7 @@ private async ValueTask CompleteMCCP2NegotiationAsync(IProtocolContext context)
396396

397397
// Initialize compression stream for server-to-client compression
398398
_compressionBuffer = new MemoryStream();
399-
#if NETSTANDARD2_1
399+
#if NETSTANDARD2_0
400400
_compressionStream = new DeflaterOutputStream(_compressionBuffer);
401401
#else
402402
_compressionStream = new ZLibStream(_compressionBuffer, CompressionMode.Compress);

TelnetNegotiationCore/Protocols/NAWSProtocol.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,8 @@ private async ValueTask CompleteNAWSAsync(StateMachine<State, Trigger>.Transitio
278278
Array.Reverse(height);
279279
}
280280

281-
ClientWidth = BitConverter.ToInt16(width);
282-
ClientHeight = BitConverter.ToInt16(height);
281+
ClientWidth = BitConverter.ToInt16(width, 0);
282+
ClientHeight = BitConverter.ToInt16(height, 0);
283283

284284
context.Logger.LogDebug("Negotiated for: {clientWidth} width and {clientHeight} height", ClientWidth, ClientHeight);
285285

0 commit comments

Comments
 (0)