/// <summary> /// Creates and returns random bytes based on internal <see cref="IUlidRng"/>. /// </summary> /// <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> /// <returns>Random bytes.</returns> /// <exception cref="InvalidOperationException"> /// Thrown when the specified <paramref name="dateTime"/> is before the last time this method was called. /// </exception> public override byte[] GetRandomBytes(DateTimeOffset dateTime) { lock (_genlock) { // Get unix time for given datetime var timestamp = Ulid.ToUnixTimeMilliseconds(dateTime); if (timestamp <= _lastgen) // Same or earlier timestamp as last time we generated random values? { // Increment our random value by one. var i = RANDLEN; while (--i >= 0 && ++_lastvalue[i] == 0) { ; } // If i made it all the way to -1 we have an overflow and we throw if (i < 0) { throw new OverflowException(); } } else // New(er) timestamp, so generate a new random value and store the new(er) timestamp { _lastvalue = _rng.GetRandomBytes(dateTime); // Use internal RNG to get bytes from _lastvalue[0] = (byte)(_lastvalue[0] & 0x7F); // Mask out bit 0 of the random part _lastgen = timestamp; // Store last timestamp } return(_lastvalue); } }
/// <summary> /// Initializes a new instance of the <see cref="MonotonicUlidRng"/> class. /// </summary> /// <param name="rng">The <see cref="IUlidRng"/> to get the random numbers from.</param> /// <param name="lastValue">The last value to 'continue from'; use <see langword="null"/> for defaults.</param> /// <exception cref="ArgumentNullException">Thrown when <paramref name="rng"/> is null.</exception> public MonotonicUlidRng(IUlidRng rng, Ulid?lastValue = null) { _rng = rng ?? throw new ArgumentNullException(nameof(rng)); _lastvalue = lastValue == null ? new byte[RANDLEN] : lastValue.Value.Random; _lastgen = Ulid.ToUnixTimeMilliseconds(lastValue == null ? Ulid.EPOCH : lastValue.Value.Time); }
/// <summary> /// Initializes a new instance of the <see cref="MonotonicUlidRng"/> class. /// </summary> /// <param name="rng">The <see cref="IUlidRng"/> to get the random numbers from.</param> /// <param name="maskMsbBits"> /// The number of (most significant) bits to mask out / set to 0 when generating a random value for a given /// timestamp /// </param> /// <param name="lastValue">The last value to 'continue from'; use <see langword="null"/> for defaults.</param> /// <exception cref="ArgumentNullException">Thrown when <paramref name="rng"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException"> /// Thrown when <paramref name="maskMsbBits"/> is less than 0 or more than 70. /// </exception> public MonotonicUlidRng(IUlidRng rng, int maskMsbBits = DEFAULTMSBMASKBITS, Ulid?lastValue = null) { _rng = rng ?? throw new ArgumentNullException(nameof(rng)); var maskbits = maskMsbBits >= 0 && maskMsbBits <= 80 - DEFAULTMSBMASKBITS ? maskMsbBits : throw new ArgumentOutOfRangeException(nameof(maskMsbBits)); _lastvalue = lastValue == null ? new byte[RANDLEN] : lastValue.Value.Random; _lastgen = Ulid.ToUnixTimeMilliseconds(lastValue == null ? Ulid.EPOCH : lastValue.Value.Time); // Prepare (or 'pre-compute') mask for (var i = 0; i < _mask.Length; i++) { var bits = maskbits > 8 ? 8 : maskbits; // Calculate number of bits to mask from this byte maskbits -= bits; // Decrease number of bits needing to mask total _mask[i] = (byte)((1 << 8 - bits) - 1); } }
/// <summary> /// Creates and returns random bytes based on internal <see cref="IUlidRng"/>. /// </summary> /// <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> /// <returns>Random bytes.</returns> /// <exception cref="InvalidOperationException"> /// Thrown when the specified <paramref name="dateTime"/> is before the last time this method was called. /// </exception> public override byte[] GetRandomBytes(DateTimeOffset dateTime) { lock (_genlock) { // Get unix time for given datetime var timestamp = Ulid.ToUnixTimeMilliseconds(dateTime); if (timestamp < _lastgen) { throw new InvalidOperationException("Clock moved backwards; this is not supported."); } if (timestamp == _lastgen) // Same timestamp as last time we generated random values? { // Increment our random value by one. var i = RANDLEN; while (--i >= 0 && ++_lastvalue[i] == 0) { ; } // If i made it all the way to -1 we have an overflow and we throw if (i < 0) { throw new OverflowException(); } } else // New(er) timestamp, so generate a new random value and store the new(er) timestamp { _lastvalue = _rng.GetRandomBytes(dateTime); // Use internal RNG to get bytes from for (var i = 0; i < _mask.Length && _mask[i] < 255; i++) // Mask out desired number of MSB's { _lastvalue[i] = (byte)(_lastvalue[i] & _mask[i]); } _lastgen = timestamp; // Store last timestamp } return(_lastvalue); } }