Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
*.yml text eol=lf

###############################################################################
# Set default behavior for command prompt diff.
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: CI

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6

- name: Setup .NET
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5
with:
dotnet-version: '8.x'

- name: Restore
run: dotnet restore

- name: Build
run: dotnet build --no-restore -c Release

- name: Test
run: dotnet test --no-build -c Release --logger "trx;LogFileName=results.trx"
40 changes: 40 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Publish

on:
push:
tags:
- 'v*'

jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6

- name: Setup .NET
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5
with:
dotnet-version: '8.x'

- name: Restore
run: dotnet restore

- name: Build
run: dotnet build --no-restore -c Release

- name: Test
run: dotnet test --no-build -c Release

- name: Pack
run: dotnet pack --no-build -c Release -o ./nupkg

- name: Login to NuGet
uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1
with:
usernameVar: NUGET_USER
passwordVar: NUGET_TOKEN

- name: Push to NuGet
run: dotnet nuget push ./nupkg/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ env.NUGET_TOKEN }} --skip-duplicate
65 changes: 65 additions & 0 deletions .github/workflows/sonar.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: SonarCloud

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
sonar:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0

- name: Setup .NET
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5
with:
dotnet-version: '8.x'

- name: Setup Java (Sonar scanner requirement)
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
with:
distribution: temurin
java-version: '17'

- name: Cache SonarCloud packages
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar

- name: Install sonar scanner and coverage tool
run: |
dotnet tool install --global dotnet-sonarscanner
dotnet tool install --global dotnet-coverage

- name: Restore
run: dotnet restore

- name: Begin Sonar analysis
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
dotnet-sonarscanner begin \
/k:"PFalkowski_StrongRandom" \
/o:"pfalkowski" \
/d:sonar.token="$SONAR_TOKEN" \
/d:sonar.host.url="https://sonarcloud.io" \
/d:sonar.cs.vscoveragexml.reportsPaths=coverage.xml \
/d:sonar.exclusions="**/*.Test/**,**/*Test.cs"

- name: Build
run: dotnet build --no-restore -c Release

- name: Collect coverage
run: dotnet-coverage collect "dotnet test --no-build -c Release" -f xml -o coverage.xml

- name: End Sonar analysis
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: dotnet-sonarscanner end /d:sonar.token="$SONAR_TOKEN"
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

namespace Extensions.Standard.Randomization.Test
{
public class BufferedRadnomProviderTest
public class BufferedRandomProviderTest
{
[Theory]
[InlineData(1)]
[InlineData(10)]
[InlineData(100)]
public void ConstructorCreatesValidInstance(int buffSize)

Check warning on line 13 in Extensions.Standard.Randomization.Test/BufferedRandomProviderTest.cs

View workflow job for this annotation

GitHub Actions / sonar

Add at least one assertion to this test case.

Check failure on line 13 in Extensions.Standard.Randomization.Test/BufferedRandomProviderTest.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add at least one assertion to this test case.

See more on https://sonarcloud.io/project/issues?id=PFalkowski_StrongRandom&issues=AZ7KL2KYzCVznm5R7M5U&open=AZ7KL2KYzCVznm5R7M5U&pullRequest=2
{
var tested = new BufferedRandomProvider(buffSize);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="NSubstitute" Version="3.1.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Extensions.Standard.Randomization\Extensions.Standard.Randomization.csproj" />
</ItemGroup>

<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>

</Project>
4 changes: 2 additions & 2 deletions Extensions.Standard.Randomization.Test/StrongRandomTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
public class StrongRandomTest
{
[Fact]
public void CtorCreatesValidInstanceWithDefault()

Check warning on line 9 in Extensions.Standard.Randomization.Test/StrongRandomTest.cs

View workflow job for this annotation

GitHub Actions / sonar

Add at least one assertion to this test case.
{
var tested = new StrongRandom(null);
}
Expand Down Expand Up @@ -221,8 +221,8 @@
{
var providerMock = new RandomProviderMock(0);
var tested = new StrongRandom(providerMock);
byte[] nullBytesArray = null;
Assert.Throws<ArgumentNullException>(() => tested.NextBytes(nullBytesArray));
byte[]? nullBytesArray = null;
Assert.Throws<ArgumentNullException>(() => tested.NextBytes(nullBytesArray!));
}
[Fact]
public void NextThrowsArgumentOutOfRangeExceptionWhenNegativeIntPassedIn()
Expand Down
6 changes: 2 additions & 4 deletions Extensions.Standard.Randomization.Test/UtilitiesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
}

[Fact]
public void RandByteTest2()

Check warning on line 33 in Extensions.Standard.Randomization.Test/UtilitiesTest.cs

View workflow job for this annotation

GitHub Actions / sonar

Add at least one assertion to this test case.
{
var repeats = 100;
var rng = new Random();
Expand Down Expand Up @@ -117,10 +117,8 @@
}
}

[Theory]
[InlineData("test")]
[InlineData(".NETStandard")]
public void NextAlphanumericRetrunsValidResult(string toChooseFrom)
[Fact]
public void NextAlphanumericRetrunsValidResult()
{
var randomSubstitute = Substitute.For<Random>();
for (int i = '0'; i < '9' + 1; ++i)
Expand Down
56 changes: 6 additions & 50 deletions Extensions.Standard.Randomization/BufferedRadnomProvider.cs
Original file line number Diff line number Diff line change
@@ -1,56 +1,12 @@
using System;
using System.Security.Cryptography;
using System;

namespace Extensions.Standard.Randomization
{
/// <summary>
/// Internally uses RNGCryptoServiceProvider. Buffered call to RNGCryptoServiceProvider up to bufferSize
/// </summary>
public class BufferedRandomProvider : IRandomProvider
/// <summary>Preserved for binary compatibility. Use <see cref="BufferedRandomProvider"/> instead (fixed spelling).</summary>
[Obsolete("Use BufferedRandomProvider instead (typo in original name fixed). This class will be removed in a future major version.")]

Check warning on line 6 in Extensions.Standard.Randomization/BufferedRadnomProvider.cs

View workflow job for this annotation

GitHub Actions / sonar

Do not forget to remove this deprecated code someday.

Check warning on line 6 in Extensions.Standard.Randomization/BufferedRadnomProvider.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not forget to remove this deprecated code someday.

See more on https://sonarcloud.io/project/issues?id=PFalkowski_StrongRandom&issues=AZ7KL2KgzCVznm5R7M5V&open=AZ7KL2KgzCVznm5R7M5V&pullRequest=2
public sealed class BufferedRadnomProvider : BufferedRandomProvider
{
public BufferedRandomProvider(int bufferSize)
{
_bufer = new Lazy<byte[]>(() => new byte[bufferSize], true);
}

private readonly Lazy<byte[]> _bufer;
private int _currentIndex;

private void GetBytesInternal(byte[] buffer)
{
using (var csprng = RandomNumberGenerator.Create())
{
csprng.GetBytes(buffer);
}
}

private void RefreshBuffer()
{
GetBytesInternal(_bufer.Value);
_currentIndex = 0;
}

public void GetBytes(byte[] input)
{
if (!_bufer.IsValueCreated)
{
RefreshBuffer();
}
if (input.Length > _bufer.Value.Length)
{
GetBytesInternal(input);
return;
}
else if (input.Length + _currentIndex > _bufer.Value.Length)
{
RefreshBuffer();
}

for (var i = 0; i < input.Length; ++i)
{
input[i] = _bufer.Value[i + _currentIndex];
}
_currentIndex += input.Length;
}
/// <inheritdoc/>
public BufferedRadnomProvider(int bufferSize) : base(bufferSize) { }
}
}
59 changes: 59 additions & 0 deletions Extensions.Standard.Randomization/BufferedRandomProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Security.Cryptography;

namespace Extensions.Standard.Randomization
{
/// <summary>
/// Buffered wrapper around <see cref="RandomNumberGenerator"/> that amortises expensive CSPRNG calls
/// by filling an internal byte buffer in one shot and dispensing bytes from it on subsequent requests.
/// </summary>
public class BufferedRandomProvider : IRandomProvider
{
private readonly Lazy<byte[]> _buffer;
private int _currentIndex;

/// <param name="bufferSize">Number of random bytes to pre-fetch per CSPRNG call.</param>
public BufferedRandomProvider(int bufferSize)
{
_buffer = new Lazy<byte[]>(() => new byte[bufferSize], true);
}

private static void GetBytesInternal(byte[] buffer)
{
using var csprng = RandomNumberGenerator.Create();
csprng.GetBytes(buffer);
}

private void RefreshBuffer()
{
GetBytesInternal(_buffer.Value);
_currentIndex = 0;
}

/// <summary>Fills <paramref name="input"/> with cryptographically-strong random bytes.</summary>
public void GetBytes(byte[] input)
{
if (!_buffer.IsValueCreated)
{
RefreshBuffer();
}

if (input.Length > _buffer.Value.Length)
{
GetBytesInternal(input);
return;
}

if (input.Length + _currentIndex > _buffer.Value.Length)
{
RefreshBuffer();
}

for (var i = 0; i < input.Length; ++i)
{
input[i] = _buffer.Value[i + _currentIndex];
}
_currentIndex += input.Length;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,30 @@
<RootNamespace>Extensions.Standard.Randomization</RootNamespace>
<AssemblyName>Extensions.Standard.Randomization</AssemblyName>
<Authors>Piotr Falkowski</Authors>
<Company></Company>
<Company>Piotr Falkowski</Company>
<Product>StrongRandom</Product>
<Description>Random interface implemented with Cryptographically-Secure Pseudo-Random Number Generator. Everywhere you use System.Random, StrongRandom can be used now. Additionally missing boilerplate extensions for any Random: NextBool, NextChar, NextString etc. added.</Description>
<Copyright>Piotr Falkowski © 2018</Copyright>
<PackageLicenseUrl>https://opensource.org/licenses/MIT</PackageLicenseUrl>
<Description>System.Random drop-in backed by a Cryptographically-Secure Pseudo-Random Number Generator. Everywhere you use System.Random, StrongRandom can be used. Extension methods for any Random: NextBool, NextChar, NextByte, NextNormal and more.</Description>
<Copyright>Piotr Falkowski © 2026</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
<PackageTags>Random, CSPRNG, Extension-methods</PackageTags>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageTags>Random, CSPRNG, Extension-methods, security, cryptography</PackageTags>
<PackageProjectUrl>https://github.com/PFalkowski/StrongRandom</PackageProjectUrl>
<RepositoryUrl>https://github.com/PFalkowski/StrongRandom</RepositoryUrl>
<PackageId>StrongRandom</PackageId>
<Version>2.0.0</Version>
<PackageReleaseNotes>Update .NET Standard sdk to 2.0</PackageReleaseNotes>
<Version>2.1.0</Version>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion Extensions.Standard.Randomization/StrongRandom.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
public class StrongRandom : Random
{
private readonly IRandomProvider _provider;
public StrongRandom(IRandomProvider provider = null)
public StrongRandom(IRandomProvider? provider = null)
{
_provider = provider ?? new BufferedRandomProvider(44);
}
Expand Down Expand Up @@ -41,7 +41,7 @@
_provider.GetBytes(buffer);
}

public override int Next()

Check warning on line 44 in Extensions.Standard.Randomization/StrongRandom.cs

View workflow job for this annotation

GitHub Actions / sonar

All 'Next' method overloads should be adjacent.

Check warning on line 44 in Extensions.Standard.Randomization/StrongRandom.cs

View workflow job for this annotation

GitHub Actions / sonar

All 'Next' method overloads should be adjacent.
{
return InternalSample();
}
Expand Down
Loading
Loading