Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d11c74e5d6 | |||
| 85721b9769 | |||
| f7484b35e2 | |||
| a490a9b328 | |||
| 034a88ba8f | |||
| e28fc62b31 | |||
| 312219d42f | |||
| 7eb3008738 |
@@ -10,13 +10,16 @@ jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup .NET
|
||||
uses: https://github.com/actions/setup-dotnet@v3
|
||||
uses: https://github.com/actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 9.x
|
||||
dotnet-version: 10.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore Core/Core.csproj
|
||||
|
||||
@@ -14,28 +14,47 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: .NET tests
|
||||
|
||||
env:
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup .NET
|
||||
uses: https://github.com/actions/setup-dotnet@v3
|
||||
uses: https://github.com/actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 9.x
|
||||
dotnet-version: |
|
||||
8.0.x
|
||||
9.0.x
|
||||
10.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet build --no-restore
|
||||
- name: Build .NET 10.0
|
||||
run: dotnet build --no-restore --framework net10.0 --configuration Release ./Core.Tests/Core.Tests.csproj
|
||||
|
||||
- name: Test
|
||||
run: dotnet test --no-build --verbosity normal --logger trx --results-directory "TestResults-9.x"
|
||||
- name: Build .NET 9.0
|
||||
run: dotnet build --no-restore --framework net9.0 --configuration Release ./Core.Tests/Core.Tests.csproj
|
||||
|
||||
- name: Build .NET 8.0
|
||||
run: dotnet build --no-restore --framework net8.0 --configuration Release ./Core.Tests/Core.Tests.csproj
|
||||
|
||||
- name: Test .NET 10.0
|
||||
run: dotnet run --no-build --framework net10.0 --configuration Release --project ./Core.Tests/Core.Tests.csproj -- -trx TestResults/results-net10.trx
|
||||
|
||||
- name: Test .NET 9.0
|
||||
run: dotnet run --no-build --framework net9.0 --configuration Release --project ./Core.Tests/Core.Tests.csproj -- -trx TestResults/results-net9.trx
|
||||
|
||||
- name: Test .NET 8.0
|
||||
run: dotnet run --no-build --framework net8.0 --configuration Release --project ./Core.Tests/Core.Tests.csproj -- -trx TestResults/results-net8.trx
|
||||
|
||||
- name: Upload dotnet test results
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: dotnet-results-9.x
|
||||
path: TestResults-9.x
|
||||
name: test-results
|
||||
path: TestResults
|
||||
if: ${{ always() }}
|
||||
retention-days: 30
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"dotnet.defaultSolution": "JustDotNet.Core.sln",
|
||||
"dotnetAcquisitionExtension.enableTelemetry": false
|
||||
"dotnetAcquisitionExtension.enableTelemetry": false,
|
||||
"dotnet.testWindow.useTestingPlatformProtocol": true
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public class Decode
|
||||
var resultString = Base32.Encode(testBytes);
|
||||
var resultBytes = Base32.Decode(resultString);
|
||||
|
||||
resultBytes.Should().BeEquivalentTo(testBytes);
|
||||
resultBytes.ShouldBeEquivalentTo(testBytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public class Decode
|
||||
public void WhenCalledWithValidString_ShouldReturnValidByteArray(string str, byte[] expected)
|
||||
{
|
||||
var actualBytesArray = Base32.Decode(str);
|
||||
actualBytesArray.Should().Equal(expected);
|
||||
actualBytesArray.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -44,7 +44,7 @@ public class Decode
|
||||
public void WhenCalledWithValidStringThatEndsWithPaddingSign_ShouldReturnValidByteArray(string testString, byte[] expected)
|
||||
{
|
||||
var actualBytesArray = Base32.Decode(testString);
|
||||
actualBytesArray.Should().Equal(expected);
|
||||
actualBytesArray.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -54,7 +54,7 @@ public class Decode
|
||||
public void WhenCalledWithValidStringWithoutPaddingSign_ShouldReturnValidByteArray(string testString, byte[] expected)
|
||||
{
|
||||
var actualBytesArray = Base32.Decode(testString);
|
||||
actualBytesArray.Should().Equal(expected);
|
||||
actualBytesArray.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -66,7 +66,7 @@ public class Decode
|
||||
public void WhenCalledWithNotValidString_ShouldThrowFormatException(string testString)
|
||||
{
|
||||
Action action = () => Base32.Decode(testString);
|
||||
action.Should().Throw<FormatException>();
|
||||
action.ShouldThrow<FormatException>();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -74,6 +74,6 @@ public class Decode
|
||||
[InlineData("")]
|
||||
public void WhenCalledWithNullString_ShouldReturnEmptyArray(string? testString)
|
||||
{
|
||||
Base32.Decode(testString).Should().BeEmpty();
|
||||
Base32.Decode(testString).ShouldBeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class Encode
|
||||
var resultBytes = Base32.Decode(testString);
|
||||
var resultString = Base32.Encode(resultBytes);
|
||||
|
||||
resultString.Should().Be(testString);
|
||||
resultString.ShouldBe(testString);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -47,7 +47,7 @@ public class Encode
|
||||
public void WhenCalledWithNotEmptyByteArray_ShouldReturnValidString(string expected, byte[] testArray)
|
||||
{
|
||||
var str = Base32.Encode(testArray);
|
||||
str.Should().Be(expected);
|
||||
str.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -56,7 +56,7 @@ public class Encode
|
||||
public void WhenCalledWithEmptyByteArray_ShouldReturnEmptyString(byte[]? testArray)
|
||||
{
|
||||
var actualBase32 = Base32.Encode(testArray);
|
||||
actualBase32.Should().Be(string.Empty);
|
||||
actualBase32.ShouldBe(string.Empty);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -68,7 +68,7 @@ public class Encode
|
||||
|
||||
var charsWritten = Base32.Encode(testArray, output);
|
||||
|
||||
charsWritten.Should().Be(0);
|
||||
output.Should().Equal(['1', '2', '3', '4']);
|
||||
charsWritten.ShouldBe(0);
|
||||
output.ShouldBe(['1', '2', '3', '4']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ public class Decode
|
||||
[InlineData(554121)]
|
||||
[InlineData(100454567)]
|
||||
[InlineData(3210589)]
|
||||
public void WhenEncodedToString_ShouldBeDecodedToTheSameByteArray(int seed)
|
||||
public void WhenBytesEncodedToString_ShouldBeDecodedToTheSameByteArray(int seed)
|
||||
{
|
||||
var rng = new Random(seed);
|
||||
|
||||
@@ -19,10 +19,47 @@ public class Decode
|
||||
var resultString = Base64Url.Encode(testBytes);
|
||||
var resultBytes = Base64Url.Decode(resultString);
|
||||
|
||||
resultBytes.Should().BeEquivalentTo(testBytes);
|
||||
resultBytes.ShouldBeEquivalentTo(testBytes);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(72121)]
|
||||
[InlineData(554121)]
|
||||
[InlineData(100454567)]
|
||||
[InlineData(3210589)]
|
||||
public void WhenLongEncodedToString_ShouldBeDecodedToTheSameLongArray(int seed)
|
||||
{
|
||||
var rng = new Random(seed);
|
||||
|
||||
for (int i = 1; i <= 512; i++)
|
||||
{
|
||||
var testLong = rng.NextInt64();
|
||||
|
||||
var resultString = Base64Url.Encode(testLong);
|
||||
var resultLong = Base64Url.DecodeLong(resultString);
|
||||
|
||||
resultLong.ShouldBe(testLong);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("RgGxr0_n1ZI", -7866126844696657594L)]
|
||||
[InlineData("sxAPfpKB5kY", 5108913293478531251L)]
|
||||
[InlineData("lO4_uitvLCg", 2894810894091415188L)]
|
||||
[InlineData("awxjIqZWz10", 6759716837247880299L)]
|
||||
[InlineData("VjNe72vug4U", -8825948697371200682L)]
|
||||
[InlineData("AAAAAAAAAAA", 0L)]
|
||||
[InlineData("__________8", -1L)]
|
||||
[InlineData("AQAAAAAAAAA", 1L)]
|
||||
[InlineData("CgAAAAAAAAA", 10L)]
|
||||
[InlineData("6AMAAAAAAAA", 1000L)]
|
||||
public void WhenCalled_ShouldReturnValidLong(string testString, long expected)
|
||||
{
|
||||
var result = Base64Url.DecodeLong(testString);
|
||||
result.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("5QrdUxDUVkCAEGw8pvLsEw", "53dd0ae5-d410-4056-8010-6c3ca6f2ec13")]
|
||||
[InlineData("6nE2uKQ4_0ar9kpmybgkdw", "b83671ea-38a4-46ff-abf6-4a66c9b82477")]
|
||||
@@ -33,7 +70,7 @@ public class Decode
|
||||
{
|
||||
var result = Base64Url.DecodeGuid(testString);
|
||||
var expected = Guid.Parse(expectedStr);
|
||||
result.Should().Be(expected);
|
||||
result.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -48,7 +85,7 @@ public class Decode
|
||||
public void WhenCalled_ShouldReturnValidBytes(string testString, byte[] expected)
|
||||
{
|
||||
var result = Base64Url.Decode(testString);
|
||||
result.Should().BeEquivalentTo(expected);
|
||||
result.ShouldBeEquivalentTo(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -60,7 +97,7 @@ public class Decode
|
||||
public void WhenCalledWithInvalidString_ShouldThrowFormatException(string testString)
|
||||
{
|
||||
Action action = () => Base64Url.Decode(testString);
|
||||
action.Should().Throw<FormatException>();
|
||||
action.ShouldThrow<FormatException>();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -72,14 +109,27 @@ public class Decode
|
||||
public void WhenCalledWithInvalidGuidString_ShouldThrowFormatException(string testString)
|
||||
{
|
||||
Action action = () => Base64Url.DecodeGuid(testString);
|
||||
action.Should().Throw<FormatException>();
|
||||
action.ShouldThrow<FormatException>();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("RgG&r0_n1ZI")]
|
||||
[InlineData("sxA fpKB5kY")]
|
||||
[InlineData("lO4_uitvL)g")]
|
||||
[InlineData("awxjIqZ^z10")]
|
||||
[InlineData("VjNe7!vug4U")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "Test case")]
|
||||
public void WhenCalledWithInvalidLongString_ShouldThrowFormatException(string testString)
|
||||
{
|
||||
Action action = () => Base64Url.DecodeLong(testString);
|
||||
action.ShouldThrow<FormatException>();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
public void WhenCalledWithNullString_ShouldReturnEmptyArray(string testString)
|
||||
public void WhenCalledWithNullString_ShouldReturnEmptyArray(string? testString)
|
||||
{
|
||||
Base64Url.Decode(testString).Should().BeEmpty();
|
||||
Base64Url.Decode(testString).ShouldBeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,24 @@ public class Encode
|
||||
{
|
||||
var testGuid = Guid.Parse(testGuidString);
|
||||
var result = Base64Url.Encode(testGuid);
|
||||
result.Should().Be(expected);
|
||||
result.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("RgGxr0_n1ZI", -7866126844696657594L)]
|
||||
[InlineData("sxAPfpKB5kY", 5108913293478531251L)]
|
||||
[InlineData("lO4_uitvLCg", 2894810894091415188L)]
|
||||
[InlineData("awxjIqZWz10", 6759716837247880299L)]
|
||||
[InlineData("VjNe72vug4U", -8825948697371200682L)]
|
||||
[InlineData("AAAAAAAAAAA", 0L)]
|
||||
[InlineData("__________8", -1L)]
|
||||
[InlineData("AQAAAAAAAAA", 1L)]
|
||||
[InlineData("CgAAAAAAAAA", 10L)]
|
||||
[InlineData("6AMAAAAAAAA", 1000L)]
|
||||
public void WhenCalledWithLong_ShouldReturnValidString(string expected, long testLong)
|
||||
{
|
||||
var result = Base64Url.Encode(testLong);
|
||||
result.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -27,28 +44,28 @@ public class Encode
|
||||
public void WhenCalled_ShouldReturnValidString(string expected, byte[] testBytes)
|
||||
{
|
||||
var result = Base64Url.Encode(testBytes);
|
||||
result.Should().Be(expected);
|
||||
result.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new byte[] { })]
|
||||
[InlineData(null)]
|
||||
public void WhenCalledWithEmptyByteArray_ShouldReturnEmptyString(byte[] testArray)
|
||||
public void WhenCalledWithEmptyByteArray_ShouldReturnEmptyString(byte[]? testArray)
|
||||
{
|
||||
var actualBase32 = Base64Url.Encode(testArray);
|
||||
actualBase32.Should().Be(string.Empty);
|
||||
actualBase32.ShouldBe(string.Empty);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new byte[] { })]
|
||||
[InlineData(null)]
|
||||
public void WhenCalledWithEmptyByteArray_ShouldReturnZeroAndNotChangeOutput(byte[] testArray)
|
||||
public void WhenCalledWithEmptyByteArray_ShouldReturnZeroAndNotChangeOutput(byte[]? testArray)
|
||||
{
|
||||
char[] output = ['1', '2', '3', '4'];
|
||||
|
||||
var charsWritten = Base64Url.Encode(testArray, output);
|
||||
|
||||
charsWritten.Should().Be(0);
|
||||
output.Should().Equal(['1', '2', '3', '4']);
|
||||
charsWritten.ShouldBe(0);
|
||||
output.ShouldBe((char[])['1', '2', '3', '4']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Exe</OutputType>
|
||||
|
||||
<AssemblyName>Just.Core.Tests</AssemblyName>
|
||||
<RootNamespace>Just.Core.Tests</RootNamespace>
|
||||
@@ -14,13 +15,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="xunit.v3" Version="3.2.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
global using Xunit;
|
||||
global using FluentAssertions;
|
||||
global using Shouldly;
|
||||
|
||||
@@ -2,15 +2,43 @@ namespace Just.Core.Tests.GuidV8Tests;
|
||||
|
||||
public class NewGuid
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(RngEntropy.Weak)]
|
||||
[InlineData(RngEntropy.Strong)]
|
||||
public void Version_And_Variant_Should_Be_Correct(RngEntropy entropy)
|
||||
{
|
||||
var rng = new Random(25);
|
||||
var referenceTime = new DateTime(2020, 05, 17, 15, 36, 13, 771, DateTimeKind.Utc);
|
||||
|
||||
for (int i = 0; i < 2000; i++)
|
||||
{
|
||||
var timestamp = referenceTime.AddSeconds(rng.Next());
|
||||
var result = GuidV8.NewGuid(timestamp, entropy);
|
||||
|
||||
#if NET9_0_OR_GREATER
|
||||
result.Version.ShouldBe(8);
|
||||
(result.Variant & 0b1100).ShouldBe(0b1000);
|
||||
#else
|
||||
var bytes = result.ToByteArray();
|
||||
// Check version (bits 4-7 of the 7th byte)
|
||||
var version = (bytes[7] >> 4) & 0x0F;
|
||||
version.ShouldBe(8); // UUID version 8
|
||||
// Check variant (bits 6-7 of the 8th byte)
|
||||
var variant = bytes[8] >> 6;
|
||||
variant.ShouldBe(0b10); // Standard UUID variant
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(RngEntropy.Weak,
|
||||
-25000000, -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000,
|
||||
-25000000, -20000000, -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000,
|
||||
-2500, -1000, -500, -499, -497, -450, -300, -200, -50, -1, 0, 1, 2, 5, 10, 20, 50, 100, 200, 350, 500, 1000,
|
||||
2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 250000000)]
|
||||
2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 20000000, 50000000, 100000000)]
|
||||
[InlineData(RngEntropy.Strong,
|
||||
-25000000, -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000,
|
||||
-25000000, -20000000, -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000,
|
||||
-2500, -1000, -500, -499, -497, -450, -300, -200, -50, -1, 0, 1, 2, 5, 10, 20, 50, 100, 200, 350, 500, 1000,
|
||||
2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 250000000)]
|
||||
2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 20000000, 50000000, 100000000)]
|
||||
[InlineData(RngEntropy.Weak,
|
||||
-9863, -9740, -9214, -8878, -8674, -8652, -8640, -8565, -8518, -8449,
|
||||
-8390, -8193, -8108, -7808, -7501, -7203, -7133, -7020, -6983, -6855,
|
||||
@@ -34,27 +62,27 @@ public class NewGuid
|
||||
6128, 6277, 6323, 6437, 6699, 6853, 7556, 7776, 7795, 8099,
|
||||
8336, 8592, 8682, 8683, 8818, 8904, 9375, 9466, 9551, 9708)]
|
||||
[InlineData(RngEntropy.Weak,
|
||||
5635912, 6673780, 17277183, 17512959, 19098799, 21672621, 30581958, 30824885, 31874213, 35192781,
|
||||
36337094, 37752116, 38387215, 39154682, 40525427, 52288093, 55218356, 59065156, 65231785, 75430932,
|
||||
76289058, 79078058, 85770685, 85925884, 94726743, 94864163, 95781967, 96150006, 96482085, 102570414,
|
||||
107768232, 110571078, 110680108, 117974892, 119800380, 126381415, 135895862, 140034471, 149039187, 150906974,
|
||||
156853001, 160514433, 166446323, 170148965, 171759448, 176494242, 184537553, 188558155, 197194403, 197615804,
|
||||
201195323, 202294490, 203040975, 203331457, 205016944, 213460258, 217072025, 217185345, 231344025, 232390198,
|
||||
235053215, 240175073, 245030721, 252275255, 252310334, 277070940, 277359970, 280624756, 288601124, 292427106,
|
||||
292563035, 299285016, 303834917, 310357836, 315078337, 316367236, 318311758, 318873972, 319675272, 321784171,
|
||||
324204294, 327667283, 330287252, 338438172, 349863360, 360777768, 366398711, 368637150, 368776734, 371900343,
|
||||
379094084, 379818879, 381448333, 381814627, 382393101, 382483709, 385600870, 389455134, 396115960, 399364095)]
|
||||
10024, 28660, 289641, 356015, 443164, 478759, 599586, 705860, 791271, 876512,
|
||||
884503, 894899, 898584, 980136, 1007927, 1680328, 1690193, 1804615, 1847117, 2005534,
|
||||
2106684, 2111936, 2252935, 2271396, 2298685, 2385409, 2414094, 2451706, 2549138, 2605538,
|
||||
2864629, 2923476, 3004288, 3182875, 3266693, 3379019, 3542110, 3851467, 3871420, 4035463,
|
||||
4316004, 4726381, 4814068, 4902666, 4979292, 4993443, 5117765, 5240585, 5249671, 5319528,
|
||||
5387595, 5434544, 5504506, 5531264, 5546173, 5780381, 5889341, 6066328, 6167883, 6185073,
|
||||
6299021, 6412136, 6621498, 6666562, 6741169, 6870279, 6949855, 6965427, 7153467, 7184150,
|
||||
7300456, 7311381, 7413697, 7505235, 7802811, 7979134, 8053665, 8177676, 8260284, 8260773,
|
||||
8269944, 8406526, 8442932, 8475162, 8555250, 8853347, 8861733, 8892200, 9069869, 9117839,
|
||||
9225445, 9245837, 9378644, 9497874, 9553625, 9650968, 9704053, 9713592, 9715054, 9735988)]
|
||||
[InlineData(RngEntropy.Strong,
|
||||
5635912, 6673780, 17277183, 17512959, 19098799, 21672621, 30581958, 30824885, 31874213, 35192781,
|
||||
36337094, 37752116, 38387215, 39154682, 40525427, 52288093, 55218356, 59065156, 65231785, 75430932,
|
||||
76289058, 79078058, 85770685, 85925884, 94726743, 94864163, 95781967, 96150006, 96482085, 102570414,
|
||||
107768232, 110571078, 110680108, 117974892, 119800380, 126381415, 135895862, 140034471, 149039187, 150906974,
|
||||
156853001, 160514433, 166446323, 170148965, 171759448, 176494242, 184537553, 188558155, 197194403, 197615804,
|
||||
201195323, 202294490, 203040975, 203331457, 205016944, 213460258, 217072025, 217185345, 231344025, 232390198,
|
||||
235053215, 240175073, 245030721, 252275255, 252310334, 277070940, 277359970, 280624756, 288601124, 292427106,
|
||||
292563035, 299285016, 303834917, 310357836, 315078337, 316367236, 318311758, 318873972, 319675272, 321784171,
|
||||
324204294, 327667283, 330287252, 338438172, 349863360, 360777768, 366398711, 368637150, 368776734, 371900343,
|
||||
379094084, 379818879, 381448333, 381814627, 382393101, 382483709, 385600870, 389455134, 396115960, 399364095)]
|
||||
10024, 28660, 289641, 356015, 443164, 478759, 599586, 705860, 791271, 876512,
|
||||
884503, 894899, 898584, 980136, 1007927, 1680328, 1690193, 1804615, 1847117, 2005534,
|
||||
2106684, 2111936, 2252935, 2271396, 2298685, 2385409, 2414094, 2451706, 2549138, 2605538,
|
||||
2864629, 2923476, 3004288, 3182875, 3266693, 3379019, 3542110, 3851467, 3871420, 4035463,
|
||||
4316004, 4726381, 4814068, 4902666, 4979292, 4993443, 5117765, 5240585, 5249671, 5319528,
|
||||
5387595, 5434544, 5504506, 5531264, 5546173, 5780381, 5889341, 6066328, 6167883, 6185073,
|
||||
6299021, 6412136, 6621498, 6666562, 6741169, 6870279, 6949855, 6965427, 7153467, 7184150,
|
||||
7300456, 7311381, 7413697, 7505235, 7802811, 7979134, 8053665, 8177676, 8260284, 8260773,
|
||||
8269944, 8406526, 8442932, 8475162, 8555250, 8853347, 8861733, 8892200, 9069869, 9117839,
|
||||
9225445, 9245837, 9378644, 9497874, 9553625, 9650968, 9704053, 9713592, 9715054, 9735988)]
|
||||
public void Guids_Differing_By_Minutes_Should_Be_Sortable(RngEntropy entropy, params int[] seconds)
|
||||
{
|
||||
var rng = new Random(25);
|
||||
@@ -69,22 +97,22 @@ public class NewGuid
|
||||
var sut = expected.Values.ToArray();
|
||||
rng.Shuffle(sut);
|
||||
|
||||
sut.Order().Should().Equal(expected.Select(x => x.Value));
|
||||
sut.OrderBy(x => x.ToString()).Should().Equal(expected.Select(x => x.Value));
|
||||
sut.Order().ShouldBe(expected.Select(x => x.Value));
|
||||
sut.OrderBy(x => x.ToString()).ShouldBe(expected.Select(x => x.Value));
|
||||
|
||||
sut.OrderDescending().Should().Equal(expected.Reverse().Select(x => x.Value));
|
||||
sut.OrderByDescending(x => x.ToString()).Should().Equal(expected.Reverse().Select(x => x.Value));
|
||||
sut.OrderDescending().ShouldBe(expected.Reverse().Select(x => x.Value));
|
||||
sut.OrderByDescending(x => x.ToString()).ShouldBe(expected.Reverse().Select(x => x.Value));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(RngEntropy.Weak,
|
||||
-250000000, -25000000, -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000,
|
||||
-100000000, -25000000, -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000,
|
||||
-2500, -1000, -500, -499, -497, -450, -300, -200, -50, -1, 0, 1, 2, 5, 10, 20, 50, 100, 200, 350, 500, 1000,
|
||||
2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 2100000000)]
|
||||
2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 200000000)]
|
||||
[InlineData(RngEntropy.Strong,
|
||||
-250000000, -25000000, -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000,
|
||||
-100000000, -25000000, -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000,
|
||||
-2500, -1000, -500, -499, -497, -450, -300, -200, -50, -1, 0, 1, 2, 5, 10, 20, 50, 100, 200, 350, 500, 1000,
|
||||
2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 2100000000)]
|
||||
2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 200000000)]
|
||||
[InlineData(RngEntropy.Weak,
|
||||
-9863, -9740, -9214, -8878, -8674, -8652, -8640, -8565, -8518, -8449,
|
||||
-8390, -8193, -8108, -7808, -7501, -7203, -7133, -7020, -6983, -6855,
|
||||
@@ -143,11 +171,11 @@ public class NewGuid
|
||||
var sut = expected.Values.ToArray();
|
||||
rng.Shuffle(sut);
|
||||
|
||||
sut.Order().Should().Equal(expected.Select(x => x.Value));
|
||||
sut.OrderBy(x => x.ToString()).Should().Equal(expected.Select(x => x.Value));
|
||||
sut.Order().ShouldBe(expected.Select(x => x.Value));
|
||||
sut.OrderBy(x => x.ToString()).ShouldBe(expected.Select(x => x.Value));
|
||||
|
||||
sut.OrderDescending().Should().Equal(expected.Reverse().Select(x => x.Value));
|
||||
sut.OrderByDescending(x => x.ToString()).Should().Equal(expected.Reverse().Select(x => x.Value));
|
||||
sut.OrderDescending().ShouldBe(expected.Reverse().Select(x => x.Value));
|
||||
sut.OrderByDescending(x => x.ToString()).ShouldBe(expected.Reverse().Select(x => x.Value));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -217,10 +245,10 @@ public class NewGuid
|
||||
var sut = expected.Values.ToArray();
|
||||
rng.Shuffle(sut);
|
||||
|
||||
sut.Order().Should().Equal(expected.Select(x => x.Value));
|
||||
sut.OrderBy(x => x.ToString()).Should().Equal(expected.Select(x => x.Value));
|
||||
sut.Order().ShouldBe(expected.Select(x => x.Value));
|
||||
sut.OrderBy(x => x.ToString()).ShouldBe(expected.Select(x => x.Value));
|
||||
|
||||
sut.OrderDescending().Should().Equal(expected.Reverse().Select(x => x.Value));
|
||||
sut.OrderByDescending(x => x.ToString()).Should().Equal(expected.Reverse().Select(x => x.Value));
|
||||
sut.OrderDescending().ShouldBe(expected.Reverse().Select(x => x.Value));
|
||||
sut.OrderByDescending(x => x.ToString()).ShouldBe(expected.Reverse().Select(x => x.Value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
namespace Just.Core.Tests.SeqIdTests;
|
||||
|
||||
public class NextId
|
||||
@@ -25,9 +26,9 @@ public class NextId
|
||||
long sequencePart = (id >> SeqShift) & SeqMask;
|
||||
long randomPart = id & RandMask;
|
||||
|
||||
timestampPart.Should().Be(500);
|
||||
sequencePart.Should().Be(0);
|
||||
randomPart.Should().BeInRange(0, RandMask);
|
||||
timestampPart.ShouldBe(500);
|
||||
sequencePart.ShouldBe(0);
|
||||
randomPart.ShouldBeInRange(0, RandMask);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -44,7 +45,7 @@ public class NextId
|
||||
long sequence1 = (id1 >> SeqShift) & SeqMask;
|
||||
long sequence2 = (id2 >> SeqShift) & SeqMask;
|
||||
|
||||
sequence2.Should().Be(sequence1 + 1);
|
||||
sequence2.ShouldBe(sequence1 + 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -61,7 +62,7 @@ public class NextId
|
||||
|
||||
// Assert
|
||||
long sequence = (id >> SeqShift) & SeqMask;
|
||||
sequence.Should().Be(0);
|
||||
sequence.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -74,7 +75,7 @@ public class NextId
|
||||
// Act & Assert
|
||||
_seqId.Next(time1); // First call sets last timestamp
|
||||
Action act = () => _seqId.Next(time2);
|
||||
act.Should().Throw<InvalidOperationException>()
|
||||
act.ShouldThrow<InvalidOperationException>()
|
||||
.WithMessage("Refused to create new SeqId. Last timestamp is in the future.");
|
||||
}
|
||||
|
||||
@@ -90,7 +91,7 @@ public class NextId
|
||||
_seqId.Next(time); // Exhauste sequence
|
||||
}
|
||||
Action act = () => _seqId.Next(time);
|
||||
act.Should().Throw<IndexOutOfRangeException>()
|
||||
act.ShouldThrow<IndexOutOfRangeException>()
|
||||
.WithMessage("Refused to create new SeqId. Sequence exhausted.");
|
||||
}
|
||||
|
||||
@@ -105,7 +106,7 @@ public class NextId
|
||||
|
||||
// Assert
|
||||
long randomPart = id & RandMask;
|
||||
randomPart.Should().BeInRange(0, RandMask);
|
||||
randomPart.ShouldBeInRange(0, RandMask);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -119,7 +120,7 @@ public class NextId
|
||||
|
||||
// Assert
|
||||
long randomPart = id & RandMask;
|
||||
randomPart.Should().BeInRange(0, RandMask);
|
||||
randomPart.ShouldBeInRange(0, RandMask);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -127,14 +128,14 @@ public class NextId
|
||||
{
|
||||
// Arrange
|
||||
var now = DateTime.UtcNow;
|
||||
var defaultEpoch = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
long expectedTimestamp = (long)(now - defaultEpoch).TotalMilliseconds;
|
||||
var defaultEpoch = SeqId.DefaultEpoch;
|
||||
long expectedTimestamp = ((long)(now - defaultEpoch).TotalMilliseconds) & TimestampMask; // Mask handles overflow
|
||||
|
||||
// Act
|
||||
long id = SeqId.NextId();
|
||||
|
||||
// Assert
|
||||
long timestampPart = (id >> TimestampShift) & TimestampMask;
|
||||
timestampPart.Should().BeCloseTo(expectedTimestamp & TimestampMask, 1); // Mask handles overflow
|
||||
timestampPart.ShouldBeInRange(expectedTimestamp, expectedTimestamp + 1);
|
||||
}
|
||||
}
|
||||
11
Core.Tests/ShouldlyExtensions.cs
Normal file
11
Core.Tests/ShouldlyExtensions.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Just.Core.Tests;
|
||||
|
||||
public static class ShouldlyExtensions
|
||||
{
|
||||
public static TException WithMessage<TException>(this TException exception, string expectedMessage, string? customMessage = null)
|
||||
where TException : Exception
|
||||
{
|
||||
exception.Message.ShouldBe(expectedMessage, customMessage: customMessage);
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ public class Populate
|
||||
|
||||
stream.Populate(buffer, offset, length);
|
||||
|
||||
buffer.Skip(offset).Take(length).Should().Equal(streamContent.Take(length));
|
||||
buffer.Skip(offset).Take(length).ShouldBe(streamContent.Take(length));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -38,7 +38,7 @@ public class Populate
|
||||
|
||||
stream.Populate(buffer);
|
||||
|
||||
buffer.Should().Equal(streamContent.Take(bufferSize));
|
||||
buffer.ShouldBe(streamContent.Take(bufferSize));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -53,6 +53,6 @@ public class Populate
|
||||
|
||||
Action action = () => stream.Populate(buffer);
|
||||
|
||||
action.Should().Throw<EndOfStreamException>();
|
||||
action.ShouldThrow<EndOfStreamException>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public class PopulateAsync
|
||||
Func<Task> action = async () => await stream.PopulateAsync(buffer, cts.Token);
|
||||
cts.Cancel();
|
||||
|
||||
await action.Should().ThrowAsync<OperationCanceledException>();
|
||||
await action.ShouldThrowAsync<OperationCanceledException>();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -31,13 +31,14 @@ public class PopulateAsync
|
||||
[InlineData(5, 5)]
|
||||
public async Task WhenCalled_ShouldPopulateSpecifiedRange(int offset, int length)
|
||||
{
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(50));
|
||||
byte[] streamContent = [0x01, 0x02, 0x03, 0x04, 0x05,];
|
||||
using var stream = new MemoryStream(streamContent);
|
||||
var buffer = new byte[10];
|
||||
|
||||
await stream.PopulateAsync(buffer, offset, length);
|
||||
await stream.PopulateAsync(buffer, offset, length, cts.Token);
|
||||
|
||||
buffer.Skip(offset).Take(length).Should().Equal(streamContent.Take(length));
|
||||
buffer.Skip(offset).Take(length).ShouldBe(streamContent.Take(length));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -47,12 +48,13 @@ public class PopulateAsync
|
||||
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, }, 5)]
|
||||
public async Task WhenStreamContainsSameOrGreaterAmmountOfItems_ShouldPopulateBuffer(byte[] streamContent, int bufferSize)
|
||||
{
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(50));
|
||||
using var stream = new MemoryStream(streamContent);
|
||||
var buffer = new byte[bufferSize];
|
||||
|
||||
await stream.PopulateAsync(buffer);
|
||||
await stream.PopulateAsync(buffer, cts.Token);
|
||||
|
||||
buffer.Should().Equal(streamContent.Take(bufferSize));
|
||||
buffer.ShouldBe(streamContent.Take(bufferSize));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -67,6 +69,6 @@ public class PopulateAsync
|
||||
|
||||
Func<Task> action = async () => await stream.PopulateAsync(buffer);
|
||||
|
||||
await action.Should().ThrowAsync<EndOfStreamException>();
|
||||
await action.ShouldThrowAsync<EndOfStreamException>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ public static class Base32
|
||||
? stackalloc char[outLength]
|
||||
: new char[outLength];
|
||||
|
||||
_ = Encode(input, output);
|
||||
var size = Encode(input, output);
|
||||
|
||||
return new string(output);
|
||||
return new string(output[..size]);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
@@ -26,20 +26,31 @@ public static class Base32
|
||||
{
|
||||
if (input.IsEmpty) return 0;
|
||||
|
||||
int outputLength = 8 * ((input.Length + 4) / 5);
|
||||
if (output.Length < outputLength)
|
||||
{
|
||||
throw new ArgumentException("Encoded input can not fit in output span.", nameof(output));
|
||||
}
|
||||
|
||||
output = output[..outputLength];
|
||||
|
||||
int i = 0;
|
||||
ReadOnlySpan<char> alphabet = Alphabet;
|
||||
Span<byte> alphabetKeys = stackalloc byte[8];
|
||||
|
||||
for (int offset = 0; offset < input.Length;)
|
||||
{
|
||||
int numCharsToOutput = GetNextGroup(input, ref offset, out byte a, out byte b, out byte c, out byte d, out byte e, out byte f, out byte g, out byte h);
|
||||
alphabetKeys.Clear();
|
||||
int numCharsToOutput = GetNextGroup(input, ref offset, alphabetKeys);
|
||||
|
||||
output[i++] = (numCharsToOutput > 0) ? alphabet[a] : Padding;
|
||||
output[i++] = (numCharsToOutput > 1) ? alphabet[b] : Padding;
|
||||
output[i++] = (numCharsToOutput > 2) ? alphabet[c] : Padding;
|
||||
output[i++] = (numCharsToOutput > 3) ? alphabet[d] : Padding;
|
||||
output[i++] = (numCharsToOutput > 4) ? alphabet[e] : Padding;
|
||||
output[i++] = (numCharsToOutput > 5) ? alphabet[f] : Padding;
|
||||
output[i++] = (numCharsToOutput > 6) ? alphabet[g] : Padding;
|
||||
output[i++] = (numCharsToOutput > 7) ? alphabet[h] : Padding;
|
||||
output[i++] = (numCharsToOutput > 0) ? alphabet[alphabetKeys[0]] : Padding;
|
||||
output[i++] = (numCharsToOutput > 1) ? alphabet[alphabetKeys[1]] : Padding;
|
||||
output[i++] = (numCharsToOutput > 2) ? alphabet[alphabetKeys[2]] : Padding;
|
||||
output[i++] = (numCharsToOutput > 3) ? alphabet[alphabetKeys[3]] : Padding;
|
||||
output[i++] = (numCharsToOutput > 4) ? alphabet[alphabetKeys[4]] : Padding;
|
||||
output[i++] = (numCharsToOutput > 5) ? alphabet[alphabetKeys[5]] : Padding;
|
||||
output[i++] = (numCharsToOutput > 6) ? alphabet[alphabetKeys[6]] : Padding;
|
||||
output[i++] = (numCharsToOutput > 7) ? alphabet[alphabetKeys[7]] : Padding;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
@@ -66,6 +77,11 @@ public static class Base32
|
||||
input = input.TrimEnd(Padding);
|
||||
|
||||
var outputLength = 5 * ((input.Length + 7) / 8);
|
||||
if (output.Length < outputLength)
|
||||
{
|
||||
throw new ArgumentException("Decoded input can not fit in output span.", nameof(output));
|
||||
}
|
||||
|
||||
output = output[..outputLength];
|
||||
output.Clear();
|
||||
|
||||
@@ -119,7 +135,7 @@ public static class Base32
|
||||
|
||||
// returns the number of bytes that were output
|
||||
[Pure]
|
||||
private static int GetNextGroup(ReadOnlySpan<byte> input, ref int offset, out byte a, out byte b, out byte c, out byte d, out byte e, out byte f, out byte g, out byte h)
|
||||
private static int GetNextGroup(ReadOnlySpan<byte> input, ref int offset, Span<byte> alphabetKeys)
|
||||
{
|
||||
var retVal = (input.Length - offset) switch
|
||||
{
|
||||
@@ -135,14 +151,14 @@ public static class Base32
|
||||
uint b4 = (offset < input.Length) ? input[offset++] : 0U;
|
||||
uint b5 = (offset < input.Length) ? input[offset++] : 0U;
|
||||
|
||||
a = (byte)(b1 >> 3);
|
||||
b = (byte)(((b1 & 0x07) << 2) | (b2 >> 6));
|
||||
c = (byte)((b2 >> 1) & 0x1f);
|
||||
d = (byte)(((b2 & 0x01) << 4) | (b3 >> 4));
|
||||
e = (byte)(((b3 & 0x0f) << 1) | (b4 >> 7));
|
||||
f = (byte)((b4 >> 2) & 0x1f);
|
||||
g = (byte)(((b4 & 0x3) << 3) | (b5 >> 5));
|
||||
h = (byte)(b5 & 0x1f);
|
||||
alphabetKeys[0] = (byte)(b1 >> 3);
|
||||
alphabetKeys[1] = (byte)(((b1 & 0x07) << 2) | (b2 >> 6));
|
||||
alphabetKeys[2] = (byte)((b2 >> 1) & 0x1f);
|
||||
alphabetKeys[3] = (byte)(((b2 & 0x01) << 4) | (b3 >> 4));
|
||||
alphabetKeys[4] = (byte)(((b3 & 0x0f) << 1) | (b4 >> 7));
|
||||
alphabetKeys[5] = (byte)((b4 >> 2) & 0x1f);
|
||||
alphabetKeys[6] = (byte)(((b4 & 0x3) << 3) | (b5 >> 5));
|
||||
alphabetKeys[7] = (byte)(b5 & 0x1f);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,41 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Just.Core;
|
||||
|
||||
public static class Base64Url
|
||||
{
|
||||
private const char Padding = '=';
|
||||
|
||||
[Pure] public static long DecodeLong(ReadOnlySpan<char> value)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNotEqual(value.Length, 11);
|
||||
|
||||
Span<byte> longBytes = stackalloc byte[8];
|
||||
Span<char> chars = stackalloc char[12];
|
||||
|
||||
value.CopyTo(chars);
|
||||
chars[^1] = Padding;
|
||||
|
||||
ReplaceNonUrlChars(chars);
|
||||
|
||||
if (!Convert.TryFromBase64Chars(chars, longBytes, out int _))
|
||||
throw new FormatException("Invalid Base64 string.");
|
||||
|
||||
return MemoryMarshal.Read<long>(longBytes);
|
||||
}
|
||||
|
||||
[Pure] public static Guid DecodeGuid(ReadOnlySpan<char> value)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNotEqual(value.Length, 22);
|
||||
|
||||
Span<byte> guidBytes = stackalloc byte[16];
|
||||
Span<char> chars = stackalloc char[24];
|
||||
|
||||
value.CopyTo(chars);
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
switch (value[i])
|
||||
{
|
||||
case '-': chars[i] = '+'; continue;
|
||||
case '_': chars[i] = '/'; continue;
|
||||
default: continue;
|
||||
}
|
||||
}
|
||||
chars[^2..].Fill('=');
|
||||
chars[^2..].Fill(Padding);
|
||||
|
||||
ReplaceNonUrlChars(chars);
|
||||
|
||||
if (!Convert.TryFromBase64Chars(chars, guidBytes, out int _))
|
||||
throw new FormatException("Invalid Base64 string.");
|
||||
|
||||
@@ -40,21 +57,14 @@ public static class Base64Url
|
||||
{
|
||||
var padding = (4 - (value.Length & 3)) & 3;
|
||||
var charlen = value.Length + padding;
|
||||
var outputBytes = charlen / 4;
|
||||
var outputBytes = 3 * (charlen / 4);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(output.Length, outputBytes);
|
||||
Span<char> chars = stackalloc char[charlen];
|
||||
|
||||
value.CopyTo(chars);
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
switch (value[i])
|
||||
{
|
||||
case '-': chars[i] = '+'; continue;
|
||||
case '_': chars[i] = '/'; continue;
|
||||
default: continue;
|
||||
}
|
||||
}
|
||||
chars[^padding..].Fill('=');
|
||||
chars[^padding..].Fill(Padding);
|
||||
|
||||
ReplaceNonUrlChars(chars);
|
||||
|
||||
if (!Convert.TryFromBase64Chars(chars, output, out outputBytes))
|
||||
throw new FormatException("Invalid Base64 string.");
|
||||
@@ -62,6 +72,18 @@ public static class Base64Url
|
||||
return outputBytes;
|
||||
}
|
||||
|
||||
[Pure] public static string Encode(in long id)
|
||||
{
|
||||
Span<byte> longBytes = stackalloc byte[8];
|
||||
MemoryMarshal.Write(longBytes, id);
|
||||
|
||||
Span<char> chars = stackalloc char[12];
|
||||
Convert.TryToBase64Chars(longBytes, chars, out int _);
|
||||
ReplaceUrlChars(chars[..^1]);
|
||||
|
||||
return new string(chars[..^1]);
|
||||
}
|
||||
|
||||
[Pure] public static string Encode(in Guid id)
|
||||
{
|
||||
Span<byte> guidBytes = stackalloc byte[16];
|
||||
@@ -69,15 +91,7 @@ public static class Base64Url
|
||||
Span<char> chars = stackalloc char[24];
|
||||
Convert.TryToBase64Chars(guidBytes, chars, out int _);
|
||||
|
||||
for (int i = 0; i < chars.Length - 2; i++)
|
||||
{
|
||||
switch (chars[i])
|
||||
{
|
||||
case '+': chars[i] = '-'; continue;
|
||||
case '/': chars[i] = '_'; continue;
|
||||
default: continue;
|
||||
}
|
||||
}
|
||||
ReplaceUrlChars(chars[..^2]);
|
||||
|
||||
return new string(chars[..^2]);
|
||||
}
|
||||
@@ -86,7 +100,7 @@ public static class Base64Url
|
||||
{
|
||||
if (input.IsEmpty) return string.Empty;
|
||||
|
||||
int outLength = 8 * ((input.Length + 5) / 6);
|
||||
int outLength = 4 * ((input.Length + 2) / 3);
|
||||
Span<char> output = stackalloc char[outLength];
|
||||
|
||||
int strlen = Encode(input, output);
|
||||
@@ -97,24 +111,47 @@ public static class Base64Url
|
||||
{
|
||||
if (input.IsEmpty) return 0;
|
||||
|
||||
var charlen = 8 * ((input.Length + 5) / 6);
|
||||
var charlen = 4 * ((input.Length + 2) / 3);
|
||||
Span<char> chars = stackalloc char[charlen];
|
||||
Convert.TryToBase64Chars(input, chars, out int charsWritten);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < charsWritten; i++)
|
||||
int i = ReplaceUrlChars(chars[..charsWritten]);
|
||||
chars[..i].CopyTo(output);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
private static int ReplaceUrlChars(Span<char> chars)
|
||||
{
|
||||
int i = 0;
|
||||
for (; i < chars.Length; i++)
|
||||
{
|
||||
switch (chars[i])
|
||||
{
|
||||
case '+': chars[i] = '-'; continue;
|
||||
case '/': chars[i] = '_'; continue;
|
||||
case '=': goto exitLoop;
|
||||
case Padding: goto break_loop;
|
||||
default: continue;
|
||||
}
|
||||
}
|
||||
exitLoop:
|
||||
chars[..i].CopyTo(output);
|
||||
break_loop:
|
||||
return i;
|
||||
}
|
||||
|
||||
private static int ReplaceNonUrlChars(Span<char> chars)
|
||||
{
|
||||
int i = 0;
|
||||
for (; i < chars.Length; i++)
|
||||
{
|
||||
switch (chars[i])
|
||||
{
|
||||
case '-': chars[i] = '+'; continue;
|
||||
case '_': chars[i] = '/'; continue;
|
||||
case Padding: goto break_loop;
|
||||
default: continue;
|
||||
}
|
||||
}
|
||||
break_loop:
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,12 @@ public class ImmutableSequence<T> :
|
||||
IReadOnlyList<T>,
|
||||
IEquatable<ImmutableSequence<T>>
|
||||
{
|
||||
private static readonly int InitialHash = typeof(ImmutableSequence<T>).GetHashCode();
|
||||
private static readonly Func<T?, T?, bool> CompareItem = EqualityComparer<T>.Default.Equals;
|
||||
private readonly ImmutableList<T> _values;
|
||||
|
||||
public ImmutableSequence() => _values = [];
|
||||
public ImmutableSequence(ImmutableList<T> values) => _values = values;
|
||||
public ImmutableSequence() : this(ImmutableArray<T>.Empty)
|
||||
{
|
||||
}
|
||||
public ImmutableSequence(IEnumerable<T> values)
|
||||
{
|
||||
_values = [..values];
|
||||
}
|
||||
public ImmutableSequence(IEnumerable<T> values) => _values = [..values];
|
||||
public ImmutableSequence(ReadOnlySpan<T> values) : this(ImmutableList.Create(values))
|
||||
{
|
||||
}
|
||||
@@ -37,10 +31,10 @@ public class ImmutableSequence<T> :
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual ImmutableSequence<T> ConstructNew(ImmutableList<T> values) => new(values);
|
||||
protected virtual ImmutableSequence<T> ConstructNew(ImmutableList<T> values) => [..values];
|
||||
|
||||
public ImmutableSequence<T> Add(T value) => ConstructNew([.._values, value]);
|
||||
public ImmutableSequence<T> AddFront(T value) => ConstructNew([value, .._values]);
|
||||
public ImmutableSequence<T> Add(T value) => ConstructNew(_values.Add(value));
|
||||
public ImmutableSequence<T> AddFront(T value) => ConstructNew(_values.Insert(0, value));
|
||||
|
||||
public ImmutableList<T>.Enumerator GetEnumerator() => _values.GetEnumerator();
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => ((IEnumerable<T>)_values).GetEnumerator();
|
||||
@@ -75,7 +69,6 @@ public class ImmutableSequence<T> :
|
||||
public override int GetHashCode()
|
||||
{
|
||||
HashCode hash = new();
|
||||
hash.Add(InitialHash);
|
||||
|
||||
foreach (var value in _values)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>Just.Core</AssemblyName>
|
||||
|
||||
@@ -1,38 +1,42 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Just.Core;
|
||||
|
||||
public static class GuidV8
|
||||
{
|
||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private const long TicksPrecision = TimeSpan.TicksPerMillisecond / 10;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Guid NewGuid(RngEntropy entropy = RngEntropy.Strong) => NewGuid(DateTime.UtcNow, entropy);
|
||||
|
||||
[Pure]
|
||||
public static Guid NewGuid(DateTime dateTime, RngEntropy entropy = RngEntropy.Strong)
|
||||
{
|
||||
var epoch = dateTime.Subtract(DateTime.UnixEpoch);
|
||||
var timestamp = epoch.Ticks / (TimeSpan.TicksPerMillisecond / 10);
|
||||
var timestamp = epoch.Ticks / TicksPrecision;
|
||||
|
||||
Span<byte> ts = stackalloc byte[8];
|
||||
MemoryMarshal.Write(ts, timestamp);
|
||||
uint tsHigh = (uint)((timestamp >> 16) & 0xFFFFFFFF);
|
||||
ushort tsLow = (ushort)(timestamp & 0x0000FFFF);
|
||||
|
||||
Span<byte> bytes = stackalloc byte[16];
|
||||
|
||||
ts[0..2].CopyTo(bytes[4..6]);
|
||||
ts[2..6].CopyTo(bytes[..4]);
|
||||
Span<byte> bytes = stackalloc byte[10];
|
||||
|
||||
if (entropy == RngEntropy.Strong)
|
||||
{
|
||||
RandomNumberGenerator.Fill(bytes[6..]);
|
||||
RandomNumberGenerator.Fill(bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
Random.Shared.NextBytes(bytes[6..]);
|
||||
Random.Shared.NextBytes(bytes);
|
||||
}
|
||||
|
||||
bytes[7] = (byte)((bytes[7] & 0x0F) | 0x80);
|
||||
bytes[0] = (byte)((bytes[0] & 0x0F) | 0x80); // Version 8
|
||||
bytes[2] = (byte)((bytes[2] & 0x1F) | 0x80); // Variant 0b1000
|
||||
|
||||
return new Guid(bytes);
|
||||
ushort version = (ushort)((bytes[0] << 8) | bytes[1]);
|
||||
|
||||
return new Guid(
|
||||
tsHigh,
|
||||
tsLow,
|
||||
version,
|
||||
bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user