diff --git a/PolyShim.Tests/NetCore21/RandomNumberGeneratorTests.cs b/PolyShim.Tests/NetCore21/RandomNumberGeneratorTests.cs new file mode 100644 index 00000000..f3c79aea --- /dev/null +++ b/PolyShim.Tests/NetCore21/RandomNumberGeneratorTests.cs @@ -0,0 +1,36 @@ +using System; +using System.Security.Cryptography; +using FluentAssertions; +using Xunit; + +namespace PolyShim.Tests.NetCore21; + +public class RandomNumberGeneratorTests +{ + [Fact] + public void Fill_Span_Test() + { + // Arrange + var span = new Span(new byte[16]); + + // Act + RandomNumberGenerator.Fill(span); + + // Assert + // Verify that not all bytes are zero (cryptographic RNG should produce non-zero data). + // Note: Theoretically, a crypto RNG could produce all zeros (probability: 2^-128), + // but this is astronomically unlikely in practice. + span.ToArray().Should().Contain(b => b != 0); + } + + [Fact] + public void Fill_EmptySpan_Test() + { + // Arrange + var span = new Span(); + + // Act & Assert + // Should not throw and should return immediately + RandomNumberGenerator.Fill(span); + } +} diff --git a/PolyShim/List-Signatures.ps1 b/PolyShim/List-Signatures.ps1 index d3edd323..5388d991 100644 --- a/PolyShim/List-Signatures.ps1 +++ b/PolyShim/List-Signatures.ps1 @@ -371,7 +371,7 @@ foreach ($file in $codeFiles) { } # Extract type declarations (must be internal) - $typePattern = 'internal\s+(?:(?:readonly|partial|static|sealed|ref)\s+)*(class|enum|struct|interface|record)\s+(\w+(?:<[^>]+>)?)' + $typePattern = 'internal\s+(?:(?:readonly|partial|static|sealed|abstract|ref)\s+)*(class|enum|struct|interface|record)\s+(\w+(?:<[^>]+>)?)' $typeMatches = [regex]::Matches($content, $typePattern) foreach ($match in $typeMatches) { diff --git a/PolyShim/NetCore10/RandomNumberGenerator.cs b/PolyShim/NetCore10/RandomNumberGenerator.cs new file mode 100644 index 00000000..a788faa5 --- /dev/null +++ b/PolyShim/NetCore10/RandomNumberGenerator.cs @@ -0,0 +1,38 @@ +#if NETSTANDARD && !NETSTANDARD1_3_OR_GREATER +#nullable enable +// ReSharper disable RedundantUsingDirective +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming +// ReSharper disable PartialTypeWithSinglePart + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace System.Security.Cryptography; + +// Polyfill for RandomNumberGenerator class on .NET Standard < 1.3 +// Note: This uses Random instead of cryptographically secure RNG as System.Security.Cryptography +// types are not available on these older platforms. +[ExcludeFromCodeCoverage] +internal abstract class RandomNumberGenerator : IDisposable +{ + public static RandomNumberGenerator Create() => new RandomWrapper(); + + public abstract void GetBytes(byte[] data); + + protected virtual void Dispose(bool disposing) { } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private sealed class RandomWrapper : RandomNumberGenerator + { + private readonly Random _random = new(); + + public override void GetBytes(byte[] data) => _random.NextBytes(data); + } +} +#endif diff --git a/PolyShim/NetCore21/RandomNumberGenerator.cs b/PolyShim/NetCore21/RandomNumberGenerator.cs new file mode 100644 index 00000000..bbce1b77 --- /dev/null +++ b/PolyShim/NetCore21/RandomNumberGenerator.cs @@ -0,0 +1,39 @@ +#if (NETCOREAPP && !NETCOREAPP2_1_OR_GREATER) || (NETFRAMEWORK) || (NETSTANDARD && !NETSTANDARD2_1_OR_GREATER) +#nullable enable +// ReSharper disable RedundantUsingDirective +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming +// ReSharper disable PartialTypeWithSinglePart + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Security.Cryptography; + +[ExcludeFromCodeCoverage] +internal static class MemberPolyfills_NetCore21_RandomNumberGenerator +{ + extension(RandomNumberGenerator) + { + // https://learn.microsoft.com/dotnet/api/system.security.cryptography.randomnumbergenerator.fill + public static void Fill(Span data) + { + if (data.Length == 0) + return; + + var rng = RandomNumberGenerator.Create(); + try + { + var buffer = new byte[data.Length]; + rng.GetBytes(buffer); + buffer.CopyTo(data); + } + finally + { + // Explicit cast needed for .NET Framework 3.5 where RandomNumberGenerator + // doesn't properly expose IDisposable for using statements. + ((IDisposable)rng).Dispose(); + } + } + } +} +#endif diff --git a/PolyShim/Signatures.md b/PolyShim/Signatures.md index b08d43b5..4eee34af 100644 --- a/PolyShim/Signatures.md +++ b/PolyShim/Signatures.md @@ -1,8 +1,8 @@ # Signatures -- **Total:** 340 -- **Types:** 74 -- **Members:** 266 +- **Total:** 343 +- **Types:** 76 +- **Members:** 267 ___ @@ -236,6 +236,8 @@ ___ - `OSPlatform` - [**[struct]**](https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.osplatform) .NET Core 1.0 - [`OSPlatform FreeBSD`](https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.osplatform.freebsd) .NET Core 3.0 +- `OSPlatformAttribute` + - [**[class]**](https://learn.microsoft.com/dotnet/api/system.runtime.versioning.osplatformattribute) .NET 5.0 - `OverloadResolutionPriorityAttribute` - [**[class]**](https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.overloadresolutionpriorityattribute) .NET 9.0 - `Parallel` @@ -275,6 +277,9 @@ ___ - [`T[] GetItems(T[], int)`](https://learn.microsoft.com/dotnet/api/system.random.getitems#system-random-getitems-1(-0()-system-int32)) .NET 8.0 - [`void NextBytes(Span)`](https://learn.microsoft.com/dotnet/api/system.random.nextbytes#system-random-nextbytes(system-span((system-byte)))) .NET Core 2.1 - [`void Shuffle(T[])`](https://learn.microsoft.com/dotnet/api/system.random.shuffle#system-random-shuffle-1(-0())) .NET 8.0 +- `RandomNumberGenerator` + - **[class]** .NET Core 1.0 + - [`void Fill(Span)`](https://learn.microsoft.com/dotnet/api/system.security.cryptography.randomnumbergenerator.fill) .NET Core 2.1 - `Range` - [**[struct]**](https://learn.microsoft.com/dotnet/api/system.range) .NET Core 3.0 - `ReadOnlyMemory`