-
-
Save RobThree/8c2e32a1323b7b500d9da11b377b4513 to your computer and use it in GitHub Desktop.
Proposed Sequential ULID Rng
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace NUlid.Rng | |
{ | |
/// <summary> | |
/// An RNG wrapper that uses a configurable increment whenever called within the same millisecond and uses the | |
/// specified RNG for generating random values for each new timeslot (ms). | |
/// </summary> | |
public class SequentialUlidRng : IUlidRng | |
{ | |
private long _lastts = -1; | |
private int _incmin = -1; | |
private int _incmax = -1; | |
private byte[] _lastvalue; | |
private IUlidRng _rng; | |
private object syncRoot = new object(); | |
private static readonly Random _incrementrng = new Random(); | |
public SequentialUlidRng() | |
: this(new CSUlidRng()) { } | |
public SequentialUlidRng(IUlidRng rng) | |
: this(rng, 0, 10000000) { } | |
public SequentialUlidRng(IUlidRng rng, int increment) | |
: this(rng, increment, increment) { } | |
public SequentialUlidRng(int increment) | |
: this(new CSUlidRng(), increment, increment) { } | |
public SequentialUlidRng(int incrementmin, int incrementmax) | |
: this(new CSUlidRng(), incrementmin, incrementmax) { } | |
public SequentialUlidRng(IUlidRng rng, int incrementmin, int incrementmax) | |
{ | |
if (rng == null) | |
throw new ArgumentNullException(nameof(rng)); | |
if (incrementmin <= 0) | |
throw new ArgumentOutOfRangeException(nameof(incrementmin)); | |
if (incrementmax <= 0) | |
throw new ArgumentOutOfRangeException(nameof(incrementmax)); | |
if (incrementmin < incrementmax) | |
throw new ArgumentException("incrementmin must be less than incrementmax"); | |
_rng = rng; | |
_incmin = incrementmin; | |
_incmax = incrementmax; | |
} | |
public byte[] GetRandomBytes(int length) | |
{ | |
lock (syncRoot) | |
{ | |
byte[] value; | |
var currentts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); | |
// Same millisecond as previous call? | |
if (currentts == _lastts) | |
{ | |
// Determine how much we're going to increment this round (either a fixed value or a random amount) | |
// Note that, when a random increment is used, this increment is *NOT* determined by a | |
// cryptographically secure RNG. This *does* make increments in the same timeslot, although random, | |
// a bit more predictable. However, we do get the improved speed of a 'normal' RNG and we can let that | |
// RNG deal with min/max instead of doing it ourselves and possibly introducing modulo bias etc. Also, | |
// next timeslot (ms) the 'sequence' will be 'broken' anyway so that mitigates predictability further. | |
var increment = (uint)(_incmin == _incmax ? _incmin : _incrementrng.Next(_incmin, _incmax)); | |
value = // result of adding increment to _lastvalue; | |
} | |
else | |
{ | |
// New timeslot, generate new random value and keep track of timeslot | |
value = _rng.GetRandomBytes(length); | |
_lastts = currentts; | |
} | |
_lastvalue = value; | |
return value; | |
} | |
} | |
byte[] IUlidRng.GetRandomBytes(int length) | |
{ | |
return this.GetRandomBytes(length); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment