using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
namespace Confuser.Core.Services {
///
/// A seeded SHA256 PRNG.
///
public class RandomGenerator {
///
/// The prime numbers used for generation
///
static readonly byte[] primes = { 7, 11, 23, 37, 43, 59, 71 };
readonly SHA256Managed sha256 = new SHA256Managed();
int mixIndex;
byte[] state; //32 bytes
int stateFilled;
///
/// Initializes a new instance of the class.
///
/// The seed.
internal RandomGenerator(byte[] seed) {
state = (byte[])seed.Clone();
stateFilled = 32;
mixIndex = 0;
}
///
/// Creates a seed buffer.
///
/// The seed data.
/// The seed buffer.
internal static byte[] Seed(string seed) {
byte[] ret;
if (!string.IsNullOrEmpty(seed))
ret = Utils.SHA256(Encoding.UTF8.GetBytes(seed));
else
ret = Utils.SHA256(Guid.NewGuid().ToByteArray());
for (int i = 0; i < 32; i++) {
ret[i] *= primes[i % primes.Length];
ret = Utils.SHA256(ret);
}
return ret;
}
///
/// Refills the state buffer.
///
void NextState() {
for (int i = 0; i < 32; i++)
state[i] ^= primes[mixIndex = (mixIndex + 1) % primes.Length];
state = sha256.ComputeHash(state);
stateFilled = 32;
}
///
/// Fills the specified buffer with random bytes.
///
/// The buffer.
/// The offset of buffer to fill in.
/// The number of random bytes.
/// is null.
///
/// or is less than 0.
///
/// Invalid or .
public void NextBytes(byte[] buffer, int offset, int length) {
if (buffer == null)
throw new ArgumentNullException("buffer");
if (offset < 0)
throw new ArgumentOutOfRangeException("offset");
if (length < 0)
throw new ArgumentOutOfRangeException("length");
if (buffer.Length - offset < length)
throw new ArgumentException("Invalid offset or length.");
while (length > 0) {
if (length >= stateFilled) {
Buffer.BlockCopy(state, 32 - stateFilled, buffer, offset, stateFilled);
offset += stateFilled;
length -= stateFilled;
stateFilled = 0;
}
else {
Buffer.BlockCopy(state, 32 - stateFilled, buffer, offset, length);
stateFilled -= length;
length = 0;
}
if (stateFilled == 0)
NextState();
}
}
///
/// Returns a random byte.
///
/// Requested random byte.
public byte NextByte() {
byte ret = state[32 - stateFilled];
stateFilled--;
if (stateFilled == 0)
NextState();
return ret;
}
///
/// Gets a buffer of random bytes with the specified length.
///
/// The number of random bytes.
/// A buffer of random bytes.
public byte[] NextBytes(int length) {
var ret = new byte[length];
NextBytes(ret, 0, length);
return ret;
}
///
/// Returns a random signed integer.
///
/// Requested random number.
public int NextInt32() {
return BitConverter.ToInt32(NextBytes(4), 0);
}
///
/// Returns a nonnegative random integer that is less than the specified maximum.
///
/// The exclusive upper bound.
/// Requested random number.
public int NextInt32(int max) {
return (int)(NextUInt32() % max);
}
///
/// Returns a random integer that is within a specified range.
///
/// The inclusive lower bound.
/// The exclusive upper bound.
/// Requested random number.
public int NextInt32(int min, int max) {
if (max <= min) return min;
return min + (int)(NextUInt32() % (max - min));
}
///
/// Returns a random unsigned integer.
///
/// Requested random number.
public uint NextUInt32() {
return BitConverter.ToUInt32(NextBytes(4), 0);
}
///
/// Returns a random double floating pointer number from 0 (inclusive) to 1 (exclusive).
///
/// Requested random number.
public double NextDouble() {
return NextUInt32() / ((double)uint.MaxValue + 1);
}
///
/// Returns a random boolean value.
///
/// Requested random boolean value.
public bool NextBoolean() {
byte s = state[32 - stateFilled];
stateFilled--;
if (stateFilled == 0)
NextState();
return s % 2 == 0;
}
///
/// Shuffles the element in the specified list.
///
///
/// The list to shuffle.
public void Shuffle(IList list) {
for (int i = list.Count - 1; i > 1; i--) {
int k = NextInt32(i + 1);
T tmp = list[k];
list[k] = list[i];
list[i] = tmp;
}
}
}
///
/// Implementation of .
///
internal class RandomService : IRandomService {
readonly byte[] seed; //32 bytes
///
/// Initializes a new instance of the class.
///
/// The project seed.
public RandomService(string seed) {
this.seed = RandomGenerator.Seed(seed);
}
///
public RandomGenerator GetRandomGenerator(string id) {
if (string.IsNullOrEmpty(id))
throw new ArgumentNullException("id");
byte[] newSeed = seed;
byte[] idHash = Utils.SHA256(Encoding.UTF8.GetBytes(id));
for (int i = 0; i < 32; i++)
newSeed[i] ^= idHash[i];
return new RandomGenerator(Utils.SHA256(newSeed));
}
}
///
/// Provides methods to obtain a unique stable PRNG for any given ID.
///
public interface IRandomService {
///
/// Gets a RNG with the specified identifier.
///
/// The identifier.
/// The requested RNG.
/// is null.
RandomGenerator GetRandomGenerator(string id);
}
}