|
1 | 1 |
|
| 2 | +using System; |
| 3 | + |
2 | 4 | using KeePassLib.Cryptography; |
3 | 5 |
|
4 | 6 | namespace KeePassDiceware |
5 | 7 | { |
6 | 8 | internal static class Extensions |
7 | 9 | { |
| 10 | + /// <summary> |
| 11 | + /// Generates a pseudorandom value in range [0, <paramref name="maxInclusive"/>] (both ends are inclusive). |
| 12 | + /// </summary> |
| 13 | + /// <param name="maxInclusive"> maximal value (inclusive) of random number.</param> |
| 14 | + /// <returns> |
| 15 | + /// Pseudorandom value from [0, <paramref name="maxInclusive"/>] range. |
| 16 | + /// </returns> |
| 17 | + public static ulong AtMost(this CryptoRandomStream random, ulong maxInclusive) |
| 18 | + { |
| 19 | + const ulong RANDOM_MAX = ulong.MaxValue; // GetRandomUInt64 max return value |
| 20 | + if (maxInclusive == RANDOM_MAX) |
| 21 | + { |
| 22 | + return random.GetRandomUInt64(); |
| 23 | + } |
| 24 | + |
| 25 | + ulong modulus = maxInclusive + 1; |
| 26 | + ulong ceil = RANDOM_MAX - (RANDOM_MAX % modulus); |
| 27 | + |
| 28 | + ulong generated; |
| 29 | + do |
| 30 | + { |
| 31 | + generated = random.GetRandomUInt64(); |
| 32 | + } while (generated >= ceil); |
| 33 | + |
| 34 | + return generated % modulus; |
| 35 | + } |
| 36 | + |
| 37 | + /// <summary> |
| 38 | + /// Generates a pseudorandom value in range [0, <paramref name="maxInclusive"/>] (both ends are inclusive). |
| 39 | + /// </summary> |
| 40 | + /// <param name="maxInclusive"> maximal value (inclusive) of random number.</param> |
| 41 | + /// <returns> |
| 42 | + /// Pseudorandom value from [0, <paramref name="maxInclusive"/>] range. |
| 43 | + /// </returns> |
| 44 | + public static int AtMost(this CryptoRandomStream random, int maxInclusive) |
| 45 | + { |
| 46 | + if (maxInclusive < 0) |
| 47 | + { |
| 48 | + throw new ArgumentOutOfRangeException(nameof(maxInclusive), $"Must be positive"); |
| 49 | + } |
| 50 | + |
| 51 | + ulong val = random.AtMost(Convert.ToUInt64(maxInclusive)); |
| 52 | + |
| 53 | + return checked((int)val); // maxInclusive <= int.MaxValue -> wont throw |
| 54 | + } |
| 55 | + |
| 56 | + /// <summary> |
| 57 | + /// Generates a pseudorandom value in range [<paramref name="minInclusive"/>, <paramref name="maxInclusive"/>] (both ends are inclusive). |
| 58 | + /// </summary> |
| 59 | + /// <param name="minInclusive"> minimal value (inclusive) of random number.</param> |
| 60 | + /// <param name="maxInclusive"> maximal value (inclusive) of random number.</param> |
| 61 | + /// <returns> |
| 62 | + /// Pseudorandom value from [<paramref name="minInclusive"/>, <paramref name="maxInclusive"/>] range. |
| 63 | + /// </returns> |
| 64 | + /// <exception cref="ArgumentOutOfRangeException"> |
| 65 | + /// Thrown when the <paramref name="minInclusive"/> is larger than <paramref name="maxInclusive"/>. |
| 66 | + /// </exception> |
8 | 67 | public static int Range(this CryptoRandomStream random, int minInclusive, int maxInclusive) |
9 | | - // #todo: avoid modulo bias |
10 | | - => (int)(random.GetRandomUInt64() % (ulong)(maxInclusive - minInclusive + 1)) + minInclusive; |
| 68 | + { |
| 69 | + if (minInclusive > maxInclusive) |
| 70 | + { |
| 71 | + throw new ArgumentOutOfRangeException(nameof(minInclusive), $"Must be smaller or equal to {nameof(maxInclusive)}"); |
| 72 | + } |
11 | 73 |
|
| 74 | + int randomValue = AtMost(random, maxInclusive - minInclusive); |
| 75 | + |
| 76 | + return randomValue + minInclusive; |
| 77 | + } |
| 78 | + |
| 79 | + /// <summary> |
| 80 | + /// Selects a pseudorandom element from <paramref name="array"/>. |
| 81 | + /// </summary> |
| 82 | + /// <param name="array"> array from which to select the random element.</param> |
| 83 | + /// <returns> |
| 84 | + /// A pseudorandom element from <paramref name="array"/>. |
| 85 | + /// </returns> |
| 86 | + /// <exception cref="ArgumentException"> |
| 87 | + /// Thrown when <paramref name="array"/> is empty. |
| 88 | + /// </exception> |
12 | 89 | public static T SelectRandom<T>(this T[] array, CryptoRandomStream random) |
13 | 90 | { |
14 | | - int choice = random.Range(0, array.Length - 1); |
| 91 | + if (array.Length <= 0) |
| 92 | + { |
| 93 | + throw new ArgumentException(nameof(array), $"Unable to select a random member of empty object."); |
| 94 | + } |
| 95 | + |
| 96 | + int choice = random.AtMost(array.Length - 1); |
| 97 | + |
15 | 98 | return array[choice]; |
16 | 99 | } |
17 | 100 |
|
18 | | - public static bool CoinToss(this CryptoRandomStream random) => (random.GetRandomUInt64() & 1) == 0; |
| 101 | + /// <summary> |
| 102 | + /// Return TRUE with 50% probability. Simulates a perfect coin toss. |
| 103 | + /// </summary> |
| 104 | + /// <returns> |
| 105 | + /// TRUE / FALSE with equal probabilities. |
| 106 | + /// </returns> |
| 107 | + public static bool CoinToss(this CryptoRandomStream random) => (random.GetRandomBytes(1)[0] & 1) == 0; |
19 | 108 | } |
20 | 109 | } |
0 commit comments