diff --git a/Server/Random.cs b/Server/Random.cs index 57b9070aa..a336b9787 100644 --- a/Server/Random.cs +++ b/Server/Random.cs @@ -21,11 +21,11 @@ namespace Server { private static readonly IRandomImpl _Random; static RandomImpl() { - if ( Core.Unix ) { - _Random = new CSPRandom(); + if (Core.Unix && File.Exists("rdrand.so")) { + _Random = new RDRandUnix(); } else if (Core.Is64Bit && File.Exists("rdrand64.dll")) { _Random = new RDRand64(); - } else if ( !Core.Is64Bit && File.Exists("rdrand32.dll") ) { + } else if (File.Exists("rdrand.dll")) { _Random = new RDRand32(); } else { _Random = new CSPRandom(); @@ -215,8 +215,148 @@ namespace Server { } } + public sealed class RDRandUnix : IRandomImpl, IHardwareRNG + { + internal class SafeNativeMethods + { + [DllImport("rdrand.so")] + internal static extern RDRandError rdrand_32(ref uint rand, bool retry); + + [DllImport("rdrand.so")] + internal static extern RDRandError rdrand_get_bytes(int n, byte[] buffer); + } + + private static int BUFFER_SIZE = 0x10000; + private static int LARGE_REQUEST = 0x40; + + private byte[] _Working = new byte[BUFFER_SIZE]; + private byte[] _Buffer = new byte[BUFFER_SIZE]; + + private int _Index = 0; + + private object _sync = new object(); + + private ManualResetEvent _filled = new ManualResetEvent(false); + + public RDRandUnix() + { + SafeNativeMethods.rdrand_get_bytes(BUFFER_SIZE, _Working); + ThreadPool.QueueUserWorkItem(new WaitCallback(Fill)); + } + + public bool IsSupported() + { + uint r = 0; + return SafeNativeMethods.rdrand_32(ref r, true) == RDRandError.Success; + } + + private void CheckSwap(int c) + { + if (_Index + c < BUFFER_SIZE) + return; + + _filled.WaitOne(); + + byte[] b = _Working; + _Working = _Buffer; + _Buffer = b; + _Index = 0; + + _filled.Reset(); + + ThreadPool.QueueUserWorkItem(new WaitCallback(Fill)); + } + + private void Fill(object o) + { + SafeNativeMethods.rdrand_get_bytes(BUFFER_SIZE, _Buffer); + _filled.Set(); + } + + private void _GetBytes(byte[] b) + { + int c = b.Length; + + lock (_sync) + { + CheckSwap(c); + Buffer.BlockCopy(_Working, _Index, b, 0, c); + _Index += c; + } + } + + private void _GetBytes(byte[] b, int offset, int count) + { + lock (_sync) + { + CheckSwap(count); + Buffer.BlockCopy(_Working, _Index, b, offset, count); + _Index += count; + } + } + + public int Next(int c) + { + return (int)(c * NextDouble()); + } + + public bool NextBool() + { + return (NextByte() & 1) == 1; + } + + private byte NextByte() + { + lock (_sync) + { + CheckSwap(1); + return _Working[_Index++]; + } + } + + public void NextBytes(byte[] b) + { + int c = b.Length; + + if (c >= LARGE_REQUEST) + { + SafeNativeMethods.rdrand_get_bytes(c, b); + return; + } + _GetBytes(b); + } + + public unsafe double NextDouble() + { + byte[] b = new byte[8]; + + if (BitConverter.IsLittleEndian) + { + b[7] = 0; + _GetBytes(b, 0, 7); + } + else + { + b[0] = 0; + _GetBytes(b, 1, 7); + } + + ulong r = 0; + fixed (byte* buf = b) + r = *(ulong*)(&buf[0]) >> 3; + + /* double: 53 bits of significand precision + * ulong.MaxValue >> 11 = 9007199254740991 + * 2^53 = 9007199254740992 + */ + + return (double)r / 9007199254740992; + } + } + public sealed class RDRand32 : IRandomImpl, IHardwareRNG { - internal class SafeNativeMethods { + internal class SafeNativeMethods + { [DllImport("rdrand32")] internal static extern RDRandError rdrand_32(ref uint rand, bool retry); diff --git a/rdrand.so b/rdrand.so new file mode 100755 index 000000000..8205f7af2 Binary files /dev/null and b/rdrand.so differ