1use anyhow::anyhow;
2use anyhow::Context;
3use anyhow::Error;
4use anyhow::Result;
5
6fn getrandom(dest: &mut [u8]) -> Result<()> {
11 getrandom::getrandom(dest).context("unable to get entropy from the OS")
12}
13
14pub fn rand_u64(lower_bound: u64, upper_bound: u64) -> Result<u64, Error> {
16 if lower_bound > upper_bound {
18 return Err(anyhow!("upper_bound should be >= lower_bound"));
19 }
20
21 let mut rand_buf = [0u8; 32];
23 getrandom(&mut rand_buf).context("unable to call getrandom for seed entropy")?;
24
25 let mut full_range = 0u64;
27 for i in 0..rand_buf.len() - 1 {
28 if i > 0 {
29 full_range = full_range.wrapping_mul(255);
30 }
31 full_range = full_range.wrapping_add(rand_buf[i] as u64);
32 }
33
34 let range = upper_bound - lower_bound;
36 let ranged_rand = full_range % (range + 1);
37 return Ok(ranged_rand + lower_bound);
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 #[test]
46 fn catch_bad_bounds() {
47 let t0 = match rand_u64(0, 0) {
48 Ok(rand) => rand,
49 Err(error) => panic!("{}", error),
50 };
51 assert_eq!(t0, 0);
52 match rand_u64(1, 0) {
53 Ok(rand) => panic!("{}", rand),
54 Err(error) => {
55 let err_str = format!("{}", error);
56 assert_eq!(err_str, "upper_bound should be >= lower_bound")
57 }
58 };
59 }
60
61 #[test]
63 fn catch_off_by_one() {
64 let mut one = false;
66 for _ in 0..1000 {
67 let result = match rand_u64(1, 1) {
68 Ok(result) => result,
69 Err(error) => {
70 panic!("{}", error);
71 }
72 };
73 if result == 1 {
74 one = true
75 } else {
76 panic!("{}", result);
77 }
78 }
79 if !one {
80 panic!("one was not hit");
81 }
82
83 let mut zero = false;
85 let mut one = false;
86 for _ in 0..1000 {
87 let result = match rand_u64(0, 1) {
88 Ok(result) => result,
89 Err(error) => {
90 panic!("{}", error);
91 }
92 };
93 if result == 0 {
94 zero = true
95 } else if result == 1 {
96 one = true
97 } else {
98 panic!("{}", result);
99 }
100 }
101 if !zero {
102 panic!("zero was not hit");
103 }
104 if !one {
105 panic!("one was not hit");
106 }
107
108 let mut one = false;
110 let mut two = false;
111 for _ in 0..1000 {
112 let result = match rand_u64(1, 2) {
113 Ok(result) => result,
114 Err(error) => {
115 panic!("{}", error);
116 }
117 };
118 if result == 1 {
119 one = true
120 } else if result == 2 {
121 two = true
122 } else {
123 panic!("{}", result);
124 }
125 }
126 if !one {
127 panic!("one was not hit");
128 }
129 if !two {
130 panic!("two was not hit");
131 }
132
133 let mut zero = false;
135 let mut one = false;
136 let mut two = false;
137 for _ in 0..1000 {
138 let result = match rand_u64(0, 2) {
139 Ok(result) => result,
140 Err(error) => {
141 panic!("{}", error);
142 }
143 };
144 if result == 0 {
145 zero = true
146 } else if result == 1 {
147 one = true
148 } else if result == 2 {
149 two = true
150 } else {
151 panic!("{}", result);
152 }
153 }
154 if !zero {
155 panic!("zero was not hit");
156 }
157 if !one {
158 panic!("one was not hit");
159 }
160 if !two {
161 panic!("two was not hit");
162 }
163 }
164
165 #[test]
168 fn check_rand_distribution() {
169 let mut one = 0u64;
170 let mut two = 0u64;
171 let mut three = 0u64;
172 for _ in 0..1_000_000 {
173 match rand_u64(1,3) {
174 Ok(result) => {
175 if result == 1 {
176 one += 1;
177 } else if result == 2 {
178 two += 1;
179 } else if result == 3 {
180 three += 1;
181 } else {
182 panic!("{}", result);
183 }
184 },
185 Err(error) => {
186 panic!("{}", error);
187 }
188 }
189 }
190
191 if one < 320_000 || two < 320_000 || three < 320_000 {
195 panic!("{} - {} - {}", one, two, three);
196 }
197 }
198}