Skip to content

Commit

Permalink
Merge
Browse files Browse the repository at this point in the history
  • Loading branch information
RobThree committed Mar 7, 2024
2 parents ca30dce + 21e9dc8 commit f6e01f5
Show file tree
Hide file tree
Showing 12 changed files with 293 additions and 54 deletions.
2 changes: 2 additions & 0 deletions NUlid.Performance/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ private static byte[] GetRandomBytes(int amount)
return b;
}

#pragma warning disable CA1822 // Mark members as static
[Benchmark(Description = "Guid.NewGuid()")]
public Guid Guid_NewGuid() => Guid.NewGuid();
[Benchmark(Description = "Ulid.NewUlid(SimpleUlidRng)")]
Expand Down Expand Up @@ -54,4 +55,5 @@ private static byte[] GetRandomBytes(int amount)
public Guid Ulid_ToGuid() => Ulid.NewUlid().ToGuid();
[Benchmark(Description = "new Ulid(Guid)")]
public Ulid New_Ulid_Guid() => new(Guid.NewGuid());
#pragma warning restore CA1822 // Mark members as static
}
9 changes: 5 additions & 4 deletions NUlid.Tests/FakeUlidRng.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using NUlid.Rng;
using System;
using System.Collections.Generic;

namespace NUlid.Tests;

Expand All @@ -13,11 +12,13 @@ namespace NUlid.Tests;
public class FakeUlidRng(byte[] desiredResult) : IUlidRng
{
//Values specifically chosen to make the result spell DEADBEEFDEADBEEF
public static readonly IReadOnlyList<byte> DEFAULTRESULT = [107, 148, 213, 185, 207, 107, 148, 213, 185, 207];
public static readonly byte[] DEFAULTRESULT = [107, 148, 213, 185, 207, 107, 148, 213, 185, 207];
private readonly byte[] _desiredresult = desiredResult;

public FakeUlidRng()
: this([.. DEFAULTRESULT]) { }
: this([.. DEFAULTRESULT]) { } // make a copy

public byte[] GetRandomBytes(DateTimeOffset dateTime) => _desiredresult;
}

public void GetRandomBytes(Span<byte> buffer, DateTimeOffset dateTime) => _desiredresult.AsSpan().CopyTo(buffer);
}
4 changes: 3 additions & 1 deletion NUlid.Tests/UlidTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ public void Ulid_Parse_Handles_Oo_TreatedAs_Zero() //https://www.crockford.com/
public void Ulid_Parse_ThrowsFormatException_OnInvalidString2() => Ulid.Parse(_knowntimestamp_string + _knownrandomseq_string.Replace('E', '{')); // Test char after last index in C2B32 array

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
[ExpectedException(typeof(ArgumentException))]
public void Ulid_Constructor_ThrowsArgumentException_OnNullByteArray() => new Ulid((byte[])null);

[TestMethod]
Expand All @@ -337,13 +337,15 @@ public void Ulid_Parse_Handles_Oo_TreatedAs_Zero() //https://www.crockford.com/
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void Ulid_NewUlid_ThrowsArgumentOutOfRangeException_OnTimestamp() => Ulid.NewUlid(Ulid.MinValue.Time.AddMilliseconds(-1));

#if NETFRAMEWORK
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void Ulid_NewUlid_ThrowsInvalidOperationException_OnRNGReturningInsufficientBytes()
{
var rng = new FakeUlidRng([1, 2, 3]);
Ulid.NewUlid(rng);
}
#endif

[TestMethod]
public void Ulid_TypeConverter_CanGetUsableConverter()
Expand Down
4 changes: 4 additions & 0 deletions NUlid/NUlid.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
<DocumentationFile>bin\release\NUlid.xml</DocumentationFile>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Memory" Version="4.5.5" />
</ItemGroup>

<ItemGroup>
<None Include="..\logo.png">
<Pack>True</Pack>
Expand Down
5 changes: 4 additions & 1 deletion NUlid/Rng/BaseUlidRng.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public abstract class BaseUlidRng : IUlidRng
/// <summary>
/// Default number of random bytes generated
/// </summary>
protected const int RANDLEN = 10;
protected internal const int RANDLEN = 10;

/// <summary>
/// Creates and returns random bytes.
Expand All @@ -19,6 +19,9 @@ public abstract class BaseUlidRng : IUlidRng
/// <returns>Random bytes.</returns>
public abstract byte[] GetRandomBytes(DateTimeOffset dateTime);

/// <inheritdoc/>
public abstract void GetRandomBytes(Span<byte> buffer, DateTimeOffset dateTime);

/// <summary>
/// Returns the default <see cref="IUlidRng"/> used when no <see cref="IUlidRng"/> is specified.
/// </summary>
Expand Down
23 changes: 23 additions & 0 deletions NUlid/Rng/CSUlidRng.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,27 @@ public override byte[] GetRandomBytes(DateTimeOffset dateTime)
return buffer;
}
#endif

/// <summary>
/// Fills the <paramref name="buffer"/> with returns cryptographically secure random bytes.
/// </summary>
/// <param name="buffer">The buffer to fill with cryptographically secure random bytes.</param>
/// <param name="dateTime">>DateTime for which the random bytes need to be generated; is ignored.</param>
/// <exception cref="ArgumentException">The buffer is too small.</exception>
public override void GetRandomBytes(Span<byte> buffer, DateTimeOffset dateTime)
{
#if NET6_0_OR_GREATER
if (buffer.Length < RANDLEN)
{
Throw(buffer.Length);
static void Throw(int len) => throw new ArgumentException($"The given buffer must be at least {RANDLEN} bytes long, actual: {len}");
}

RandomNumberGenerator.Fill(buffer);
#else
var tmp = new byte[RANDLEN];
_rng.GetBytes(tmp);
tmp.AsSpan().CopyTo(buffer);
#endif
}
}
7 changes: 7 additions & 0 deletions NUlid/Rng/IUlidRng.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,11 @@ public interface IUlidRng
/// <param name="dateTime">DateTime for which the random bytes need to be generated; can be ignored but provides context.</param>
/// <returns>Random bytes.</returns>
byte[] GetRandomBytes(DateTimeOffset dateTime);

/// <summary>
/// Fills the <paramref name="buffer"/> with random bytes.
/// </summary>
/// <param name="buffer">The buffer to fill with random bytes.</param>
/// <param name="dateTime">DateTime for which the random bytes need to be generated; can be ignored but provides context.</param>
void GetRandomBytes(Span<byte> buffer, DateTimeOffset dateTime);
}
32 changes: 31 additions & 1 deletion NUlid/Rng/MicrosecondUlidRng.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,37 @@ public MicrosecondUlidRng(IUlidRng rng, int bits = 14)
public byte[] GetRandomBytes(DateTimeOffset dateTime)
{
var buffer = _rng.GetRandomBytes(dateTime);
Core(buffer, dateTime);
return buffer;
}

/// <summary>
/// Fills <paramref name="buffer"/> with random bytes based on internal <see cref="IUlidRng"/> and the microseconds of
/// the given <paramref name="dateTime"/>.
/// </summary>
/// <param name="buffer">The buffer to fill with random bytes.</param>
/// <param name="dateTime">
/// DateTime for which the random bytes need to be generated; this value is used to determine wether a sequence
/// needs to be incremented (same timestamp with millisecond resolution) or reset to a new random value.
/// </param>
/// <exception cref="ArgumentException">The buffer is too small.</exception>
/// <exception cref="InvalidOperationException">
/// Thrown when the specified <paramref name="dateTime"/> is before the last time this method was called.
/// </exception>
public void GetRandomBytes(Span<byte> buffer, DateTimeOffset dateTime)
{
if (buffer.Length < BaseUlidRng.RANDLEN)
{
Throw(buffer.Length);
static void Throw(int len) => throw new ArgumentException($"The given buffer must be at least {BaseUlidRng.RANDLEN} bytes long, actual: {len}");
}

_rng.GetRandomBytes(buffer, dateTime);
Core(buffer, dateTime);
}

private void Core(Span<byte> buffer, DateTimeOffset dateTime)
{
// Extract microseconds from timestamp (14 bits max), align microseconds-MSB (14 bits) to ushort MSB (16 bits) by shifting left 2
// bits. Then mask out undesired bits
var usecpart = (ushort)(((dateTime.Ticks % 10000) << 2) & _mask);
Expand All @@ -57,6 +88,5 @@ public byte[] GetRandomBytes(DateTimeOffset dateTime)
// of random data with the microsecondpart
buffer[0] = (byte)((buffer[0] & _negmaskmsb) | (usecpart >> 8));
buffer[1] = (byte)((buffer[0] & _negmasklsb) | (usecpart & 0xFF));
return buffer;
}
}
34 changes: 31 additions & 3 deletions NUlid/Rng/MonotonicUlidRng.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,36 @@ public MonotonicUlidRng()
/// Thrown when the specified <paramref name="dateTime"/> is before the last time this method was called.
/// </exception>
public override byte[] GetRandomBytes(DateTimeOffset dateTime)
{
var buffer = new byte[RANDLEN];
Core(buffer, dateTime);
return buffer;
}

/// <summary>
/// Fills the <paramref name="buffer"/> with random bytes based on internal <see cref="IUlidRng"/>.
/// </summary>
/// <param name="buffer">The buffer to fill with random bytes.</param>
/// <param name="dateTime">
/// DateTime for which the random bytes need to be generated; this value is used to determine wether a sequence
/// needs to be incremented (same timestamp with millisecond resolution) or reset to a new random value.
/// </param>
/// <exception cref="ArgumentException">The buffer is too small.</exception>
/// <exception cref="InvalidOperationException">
/// Thrown when the specified <paramref name="dateTime"/> is before the last time this method was called.
/// </exception>
public override void GetRandomBytes(Span<byte> buffer, DateTimeOffset dateTime)
{
if (buffer.Length < RANDLEN)
{
Throw(buffer.Length);
static void Throw(int len) => throw new ArgumentException($"The given buffer must be at least {RANDLEN} bytes long, actual: {len}");
}

Core(buffer, dateTime);
}

private void Core(Span<byte> buffer, DateTimeOffset dateTime)
{
lock (_genlock)
{
Expand All @@ -83,9 +113,7 @@ public override byte[] GetRandomBytes(DateTimeOffset dateTime)

_lastgen = timestamp; // Store last timestamp
}
var buffer = new byte[RANDLEN];
_lastvalue.CopyTo(buffer, 0);
return buffer;
_lastvalue.AsSpan().CopyTo(buffer);
}
}
}
23 changes: 23 additions & 0 deletions NUlid/Rng/SimpleUlidRng.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,29 @@ public override byte[] GetRandomBytes(DateTimeOffset dateTime)
return _buffer;
}

/// <summary>
/// Fills the <paramref name="buffer"/> with random bytes.
/// </summary>
/// <param name="buffer">The buffer to fill with random bytes.</param>
/// <param name="dateTime">DateTime for which the random bytes need to be generated; is ignored.</param>
/// <exception cref="ArgumentException">The buffer is too small.</exception>
public override void GetRandomBytes(Span<byte> buffer, DateTimeOffset dateTime)
{
if (buffer.Length < RANDLEN)
{
Throw(buffer.Length);
static void Throw(int len) => throw new ArgumentException($"The given buffer must be at least {RANDLEN} bytes long, actual: {len}");
}

#if NET6_0_OR_GREATER
Random.Shared.NextBytes(buffer);
#else
var tmp = new byte[RANDLEN];
ThreadLocalRandom.Instance.NextBytes(tmp);
tmp.AsSpan().CopyTo(buffer);
#endif
}

/// <summary>
/// Convenience class for dealing with randomness.
/// </summary>
Expand Down
Loading

0 comments on commit f6e01f5

Please sign in to comment.