From ff3a28ab8488f6e844028aacd5ac46aba539ea76 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sat, 25 Jul 2020 15:57:32 -0700 Subject: [PATCH] Fixes RNG (#181) --- Projects/Server/Random/BaseRandomSource.cs | 173 ++++++++++++++++++ Projects/Server/Random/IRandomSource.cs | 46 +++++ .../RandomSources.cs} | 25 +-- .../{Utilities => Random}/SecureRandom.cs | 28 +-- .../Xoshiro256PlusPlus.cs | 63 +------ Projects/Server/Utilities/Utility.cs | 45 ++--- .../Security/Argon2PasswordProtection.cs | 5 +- .../Engines/Harvest/Core/HarvestDefinition.cs | 3 +- 8 files changed, 273 insertions(+), 115 deletions(-) create mode 100644 Projects/Server/Random/BaseRandomSource.cs create mode 100644 Projects/Server/Random/IRandomSource.cs rename Projects/Server/{Utilities/RandomProviders.cs => Random/RandomSources.cs} (66%) rename Projects/Server/{Utilities => Random}/SecureRandom.cs (64%) rename Projects/Server/{Utilities => Random}/Xoshiro256PlusPlus.cs (73%) diff --git a/Projects/Server/Random/BaseRandomSource.cs b/Projects/Server/Random/BaseRandomSource.cs new file mode 100644 index 000000000..2823fc4f1 --- /dev/null +++ b/Projects/Server/Random/BaseRandomSource.cs @@ -0,0 +1,173 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: BaseRandomSource.cs - Created: 2020/07/25 - Updated: 2020/07/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Server.Random +{ + public abstract class BaseRandomSource : IRandomSource + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Log2(uint v) + { + int exp = BitOperations.Log2(v); + return v == 1 << exp ? exp : exp + 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Log2(ulong v) + { + int exp = BitOperations.Log2(v); + return v == 1UL << exp ? exp : exp + 1; + } + + const double INCR_DOUBLE = 1.0 / (1UL << 53); + const float INCR_FLOAT = 1f / (1U << 24); + + public abstract ulong NextULong(); + public abstract void NextBytes(Span buffer); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Next() + { + ulong rtn; + do rtn = NextULong() >> 33; + while(rtn == 0x7fff_ffffUL); + + return (int)rtn; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Next(int count) + { + if (count == 0) throw new ArgumentOutOfRangeException(nameof(count), count, "count must not be 0"); + if (count == 1 || count == -1) return 0; + + bool negative = count < 0; + + int max = negative ? -count : count; + + int bits = Log2((uint)max); + + int x; + do x = (int)(NextULong() >> (64 - bits)); + while (x >= max); + + return negative ? -x : x; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Next(int minValue, int count) => minValue + Next(count); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Next(uint count) + { + if (count == 0) throw new ArgumentOutOfRangeException(nameof(count), count, "count must not be 0"); + if (count == 1) return 0; + + int bits = Log2(count); + + uint x; + do x = (uint)(NextULong() >> (64 - bits)); + while (x >= count); + + return x; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Next(uint minValue, uint count) => minValue + Next(count); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long Next(long count) + { + if (count == 0) throw new ArgumentOutOfRangeException(nameof(count), count, "count must not be 0"); + if (count == 1 || count == -1) return 0; + + bool negative = count < 0; + + long max = negative ? -count : count; + + int bits = Log2((ulong)max); + + long x; + do x = (long)(NextULong() >> (64 - bits)); + while (x >= max); + + return negative ? -x : x; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long Next(long minValue, long count) => minValue + Next(count); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double NextDouble() => (NextULong() >> 11) * INCR_DOUBLE; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int NextInt() => (int)(NextULong() >> 33); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint NextUInt() => (uint)NextULong(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool NextBool() => (NextULong() & 0x8000000000000000) != 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte NextByte() => (byte)(NextULong() >> 56); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float NextFloat() => (NextULong() >> 40) * INCR_FLOAT; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float NextFloatNonZero() => NextFloat() + INCR_FLOAT; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double NextDoubleNonZero() => NextDouble() + INCR_DOUBLE; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double NextDoubleHighRes() + { + int exponent = -64; + ulong significand; + int shift; + + while ((significand = NextULong()) == 0) + { + exponent -= 64; + + if (exponent < -1074) + return 0; + } + + shift = BitOperations.LeadingZeroCount(significand); + if (shift != 0) + { + exponent -= shift; + significand <<= shift; + significand |= NextULong() >> (64 - shift); + } + + significand |= 1; + + return significand * Math.Pow(2, exponent); + } + } +} diff --git a/Projects/Server/Random/IRandomSource.cs b/Projects/Server/Random/IRandomSource.cs new file mode 100644 index 000000000..4ae96be4d --- /dev/null +++ b/Projects/Server/Random/IRandomSource.cs @@ -0,0 +1,46 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: IRandomSource.cs - Created: 2020/07/25 - Updated: 2020/07/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; + +namespace Server.Random +{ + public interface IRandomSource + { + int Next(); + int Next(int maxValue); + int Next(int minValue, int count); + uint Next(uint maxValue); + uint Next(uint minValue, uint count); + long Next(long maxValue); + long Next(long minValue, long count); + double NextDouble(); + void NextBytes(Span buffer); + int NextInt(); + uint NextUInt(); + ulong NextULong(); + bool NextBool(); + byte NextByte(); + float NextFloat(); + float NextFloatNonZero(); + double NextDoubleNonZero(); + double NextDoubleHighRes(); + } +} diff --git a/Projects/Server/Utilities/RandomProviders.cs b/Projects/Server/Random/RandomSources.cs similarity index 66% rename from Projects/Server/Utilities/RandomProviders.cs rename to Projects/Server/Random/RandomSources.cs index c49eb8920..9f9a592b0 100644 --- a/Projects/Server/Utilities/RandomProviders.cs +++ b/Projects/Server/Random/RandomSources.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright (C) 2019-2020 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: RandomProviders.cs - Created: 2020/05/24 - Updated: 2020/05/24 * + * File: RandomSources.cs - Created: 2020/05/24 - Updated: 2020/07/25 * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -18,25 +18,14 @@ * along with this program. If not, see . * *************************************************************************/ -using System; - -namespace Server +namespace Server.Random { - public interface IRandomProvider + public static class RandomSources { - public ulong Next(ulong max); - public uint Next(uint max); - public bool NextBool(); - public void GetBytes(Span b); - public double NextDouble(); - } + private static IRandomSource m_Source; + private static IRandomSource m_SecureSource; - public static class RandomProviders - { - private static IRandomProvider m_Provider; - private static IRandomProvider m_SecureProvider; - - public static IRandomProvider Provider => m_Provider ??= new Xoshiro256PlusPlus(); - public static IRandomProvider SecureProvider => m_SecureProvider ??= new SecureRandom(); + public static IRandomSource Source => m_Source ??= new Xoshiro256PlusPlus(); + public static IRandomSource SecureSource => m_SecureSource ??= new SecureRandom(); } } diff --git a/Projects/Server/Utilities/SecureRandom.cs b/Projects/Server/Random/SecureRandom.cs similarity index 64% rename from Projects/Server/Utilities/SecureRandom.cs rename to Projects/Server/Random/SecureRandom.cs index e8807e8a8..7edd1dc7d 100644 --- a/Projects/Server/Utilities/SecureRandom.cs +++ b/Projects/Server/Random/SecureRandom.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright (C) 2019-2020 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: Random.cs - Created: 2019/12/30 - Updated: 2020/05/23 * + * File: SecureRandom.cs - Created: 2020/01/09 - Updated: 2020/07/25 * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -19,24 +19,28 @@ *************************************************************************/ using System; +using System.Buffers.Binary; +using System.Runtime.CompilerServices; using System.Security.Cryptography; +using Server.Random; namespace Server { - public class SecureRandom : RandomNumberGenerator, IRandomProvider + public class SecureRandom : BaseRandomSource { - private readonly RandomNumberGenerator m_Random = new RNGCryptoServiceProvider(); + private RandomNumberGenerator m_Random; - public override void GetBytes(byte[] data) => m_Random.GetBytes(data); + public RandomNumberGenerator Generator => m_Random ??= new RNGCryptoServiceProvider(); - public override void GetBytes(Span data) => m_Random.GetBytes(data); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override ulong NextULong() + { + Span buffer = stackalloc byte[sizeof(ulong)]; + Generator.GetBytes(buffer); + return BinaryPrimitives.ReadUInt64BigEndian(buffer); + } - public ulong Next(ulong c) => throw new NotImplementedException(); - - public uint Next(uint c) => throw new NotImplementedException(); - - public bool NextBool() => throw new NotImplementedException(); - - public double NextDouble() => throw new NotImplementedException(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void NextBytes(Span buffer) => Generator.GetBytes(buffer); } } diff --git a/Projects/Server/Utilities/Xoshiro256PlusPlus.cs b/Projects/Server/Random/Xoshiro256PlusPlus.cs similarity index 73% rename from Projects/Server/Utilities/Xoshiro256PlusPlus.cs rename to Projects/Server/Random/Xoshiro256PlusPlus.cs index fce281e60..f29687efa 100644 --- a/Projects/Server/Utilities/Xoshiro256PlusPlus.cs +++ b/Projects/Server/Random/Xoshiro256PlusPlus.cs @@ -3,7 +3,7 @@ * Copyright (C) 2019-2020 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Xoshiro256PlusPlus.cs * - * Created: 2019/12/29 - Updated: 2020/05/24 * + * Created: 2020/01/09 - Updated: 2020/07/25 * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -21,12 +21,10 @@ using System; using System.Runtime.CompilerServices; -using System.Security.Cryptography; -using Server.Utilities; -namespace Server +namespace Server.Random { - public class Xoshiro256PlusPlus : RandomNumberGenerator, IRandomProvider + public class Xoshiro256PlusPlus : BaseRandomSource { private ulong _s0, _s1, _s2, _s3; @@ -54,29 +52,7 @@ namespace Server } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint Next(uint max) - { - if (max <= 1u << 12) - { - if (max == 0) throw new ArgumentOutOfRangeException(nameof(max)); - return (uint)(((ulong)NextUInt32() * max) >> 32); - } - - uint r, v, limit = (uint)-(int)max; - do - { - r = NextUInt32(); - v = r % max; - } while (r - v > limit); - - return v; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint NextUInt32() => (uint)NextUInt64(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong NextUInt64() + public override ulong NextULong() { var r1 = (_s1 << 2) + _s1; var r2 = (r1 << 7) | (r1 >> 57); @@ -97,32 +73,7 @@ namespace Server } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong Next(ulong max) - { - if (max <= uint.MaxValue) return Next((uint)max); - - if (max <= 1ul << 38) return (((NextUInt32() * max) >> 32) + (NextUInt32() & ((1u << 26) - 1)) * max) >> 26; - - ulong r, v, limit = (ulong)-(long)max; - do - { - r = NextUInt64(); - v = r % max; - } while (r - v > limit); - - return v; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool NextBool() => NextUInt64() < 1ul << 63; - - public override void GetBytes(byte[] data) - { - if (data == null) throw new ArgumentNullException(nameof(data)); - GetBytes(new Span(data)); - } - - public override unsafe void GetBytes(Span b) + public override unsafe void NextBytes(Span b) { if (b.Length == 0) return; @@ -187,8 +138,6 @@ namespace Server _s3 = s3; } - public double NextDouble() => (NextUInt64() >> 11) * (1.0 / (1ul << 53)); - private static readonly ulong[] JUMP = { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c }; @@ -217,7 +166,7 @@ namespace Server s3 ^= _s3; } - NextUInt64(); + NextULong(); } _s0 = s0; diff --git a/Projects/Server/Utilities/Utility.cs b/Projects/Server/Utilities/Utility.cs index ee9eda78d..ab053528d 100644 --- a/Projects/Server/Utilities/Utility.cs +++ b/Projects/Server/Utilities/Utility.cs @@ -28,6 +28,7 @@ using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Text; using System.Xml; +using Server.Random; namespace Server { @@ -854,7 +855,7 @@ namespace Server var total = 0; for (var i = 0; i < amount; ++i) - total += (int)RandomProviders.Provider.Next(sides) + 1; + total += (int)RandomSources.Source.Next(1, sides); return total + bonus; } @@ -864,19 +865,23 @@ namespace Server var count = list.Count; for (var i = count - 1; i > 0; i--) { - var r = (int)RandomProviders.Provider.Next((uint)count); + var r = RandomSources.Source.Next(count); var swap = list[r]; list[r] = list[i]; list[i] = swap; } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int RandomList(params int[] list) => RandomList(list); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T RandomList(IList list) => list[Random(list.Count)]; - public static bool RandomBool() => RandomProviders.Provider.NextBool(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool RandomBool() => RandomSources.Source.NextBool(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int RandomMinMax(int min, int max) { if (min > max) @@ -890,27 +895,23 @@ namespace Server return min; } - return min + (int)RandomProviders.Provider.Next((uint)(max - min + 1)); + return min + (int)RandomSources.Source.Next((uint)(max - min + 1)); } - public static int Random(int from, int count) - { - if (count == 0) return from; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Random(int from, int count) => RandomSources.Source.Next(from, count); - if (count > 0) return (int)(from + RandomProviders.Provider.Next((uint)count)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Random(int count) => RandomSources.Source.Next(count); - return (int)(from - RandomProviders.Provider.Next((uint)-count)); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Random(uint count) => RandomSources.Source.Next(count); - public static int Random(int count) => (int)RandomProviders.Provider.Next((uint)count); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RandomBytes(Span buffer) => RandomSources.Source.NextBytes(buffer); - public static uint Random(uint count) => RandomProviders.Provider.Next(count); - - public static void RandomBytes(byte[] buffer) => RandomProviders.Provider.GetBytes(buffer); - - public static void RandomBytes(Span buffer) => RandomProviders.Provider.GetBytes(buffer); - - public static double RandomDouble() => RandomProviders.Provider.NextDouble(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double RandomDouble() => RandomSources.Source.NextDouble(); /// /// Random pink, blue, green, orange, red or yellow hue @@ -989,13 +990,7 @@ namespace Server /// public static int RandomMetalHue() => Random(2401, 30); - public static int ClipDyedHue(int hue) - { - if (hue < 2) - return 2; - - return hue > 1001 ? 1001 : hue; - } + public static int ClipDyedHue(int hue) => hue < 2 ? 2 : hue > 1001 ? 1001 : hue; /// /// Random hue in the range 2-1001 diff --git a/Projects/UOContent/Accounting/Security/Argon2PasswordProtection.cs b/Projects/UOContent/Accounting/Security/Argon2PasswordProtection.cs index ecf1be8e0..0451cf5e2 100644 --- a/Projects/UOContent/Accounting/Security/Argon2PasswordProtection.cs +++ b/Projects/UOContent/Accounting/Security/Argon2PasswordProtection.cs @@ -3,7 +3,7 @@ * Copyright (C) 2019-2020 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Argon2PasswordProtection.cs * - * Created: 2020/05/01 - Updated: 2020/05/24 * + * Created: 2020/05/01 - Updated: 2020/07/25 * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -20,6 +20,7 @@ *************************************************************************/ using System.Security.Cryptography; +using Server.Random; namespace Server.Accounting.Security { @@ -27,7 +28,7 @@ namespace Server.Accounting.Security { public static IPasswordProtection Instance = new Argon2PasswordProtection(); private Argon2PasswordHasher m_PasswordHasher = new Argon2PasswordHasher( - rng: RandomProviders.SecureProvider as RandomNumberGenerator + rng: (RandomSources.SecureSource as SecureRandom)?.Generator ); public string EncryptPassword(string plainPassword) => diff --git a/Projects/UOContent/Engines/Harvest/Core/HarvestDefinition.cs b/Projects/UOContent/Engines/Harvest/Core/HarvestDefinition.cs index 33467ad76..559db0a85 100644 --- a/Projects/UOContent/Engines/Harvest/Core/HarvestDefinition.cs +++ b/Projects/UOContent/Engines/Harvest/Core/HarvestDefinition.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Server.Random; namespace Server.Engines.Harvest { @@ -116,7 +117,7 @@ namespace Server.Engines.Harvest if (RandomizeVeins) return GetVeinFrom(Utility.Random(1000u)); // TODO: Introduce pulling primes from a config and writing them if they don't exist to the config - using Xoshiro256PlusPlus random = new Xoshiro256PlusPlus((ulong)(x * 17 + y * 11 + map.MapID * 3)); + var random = new Xoshiro256PlusPlus((ulong)(x * 17 + y * 11 + map.MapID * 3)); return GetVeinFrom(random.Next(VeinWeights)); }