onlinecode/
rng.rs

1//! # A random-number generator for Online Codes
2//!
3//! This implements required traits from the rand_core crate to get a
4//! full-featured set of calling methods.
5//!
6//! Design is simply to feed the current state of the RNG (a 160-bit
7//! number) into SHA-1.
8
9
10// New approach
11//
12// I was going to be compatible with Rng, but distributions are a step
13// too far. I want to focus on compatibility between Rust, C and Perl,
14// so I will rewrite everything to use RngCore::next_u32 as the only
15// rng call that I use. Everything else will be built on this...
16//
17// For a rand between 0 (included) and 1 (not included):
18//
19//     return ((rng.next_u32() * u32::MAX) >> 32) as f64
20//
21// When converting a table of f64 floating points that are in the
22// range 0 <= float < 1 into [0 .. u32::MAX - 1]:
23//
24//     return ((float as u64 * u32::MAX)  >> 32) as u32
25//
26// Treat 1.0 in that case as u32::MAX (table sentinel)
27//
28// max_degree uses such a table, so it will call `rng.next_u32()` and
29// use exactly the same logic as before, only working with unsigned
30// 32-bit integers.
31//
32// floyd() needs integers in the range `0..j` (not including j). The
33// operation is:
34//
35//     let pick : u32 = ((rng.next_u32() as u64 * j) >> 32).into()
36//
37// I think that I'll move every bit of code that uses random numbers
38// into the same module. 
39//
40// For conformance testing, I'll generate lists of random numbers,
41// calls to max_degree (which uses the probability distribution table)
42// and calls to floyd (which generates ints in a range) and calculate
43// the SHA-1 sum of the output (in a canonical format). Then I can
44// simply check whether the hashes differ or not. That should be
45// enough to be confident that the implementations are interoperable.
46//
47// Given a choice between special handling of 0xffffffff (by
48// rerolling) and breaking backwards compatibility by using a better
49// scheme, I'll go with the better scheme.
50
51use crate::*;
52
53use rand::{Rng, SeedableRng};
54use rand_core::{RngCore, Error, impls};
55
56use sha1::{Sha1, Digest};
57
58// #[derive(Debug,Copy,Clone)]
59pub struct SHA1Rng {
60
61//    state : <SHA1Rng as rand::SeedableRng>::Seed,
62    state : [u8; 20],
63    
64    // with 160 bits of state, we could spin up a new digest every
65    // time we need a new random number, or we could consume 32 bits
66    // of it at a time, only producing a new hash when we have run out
67    // of bits. We can generate 5 32-bit values from 160 bits.
68
69    state_bytes : usize,	// for SHA-1, 160 bits = 20 bytes
70    avail_bytes : usize,	// count down to zero
71
72    always_hash : bool,
73}
74
75impl SHA1Rng {
76    // use fluent/builder-style setup of optional always_hash flag
77    // since we can't pass any extra arguments to
78    // SeedableRng::from_seed(). (Could also provide a generic
79    // constructor here)
80    pub fn always_hash(mut self) -> Self { self.always_hash = true; self }
81    pub fn never_hash(mut self) -> Self  { self.always_hash = false; self }
82
83    // move to next internal state: state <- sha1(state)
84    pub fn next_hash(&mut self) {
85	let hash = sha1::Sha1::digest(&self.state);
86	self.state.copy_from_slice(&hash);
87	self.avail_bytes = self.state_bytes;
88    }
89
90    // convenience constructors
91    pub fn from_string(s : &String) -> Self {
92	// 
93	let hash = sha1::Sha1::digest(s.as_bytes());
94	let mut output = [0u8; 20];
95	output.copy_from_slice(&hash);
96	Self::from_seed(output)
97    }
98    pub fn from_str(s : &str) -> Self {
99	// 
100	let hash = sha1::Sha1::digest(s.as_bytes());
101	let mut output = [0u8; 20];
102	output.copy_from_slice(&hash);
103	Self::from_seed(output)
104    }
105
106    // TODO: implement floating-point rand that works like C/Perl
107    // probably involves implementing custom Distribution to replace
108    // Standard
109}
110
111impl SeedableRng for SHA1Rng {
112    type Seed = [u8; 20];
113
114    fn from_seed(seed: Self::Seed) -> Self {
115	SHA1Rng {
116	    state : seed,
117	    state_bytes : 20,
118	    avail_bytes : 20,
119	    always_hash : true
120	}
121    }
122}
123
124
125impl RngCore for SHA1Rng {
126    // prefer 32-bit numbers to avoid precision errors when
127    // calculating. They will be upgraded to u64/f64
128
129    // Notice that when avoiding hash, we return parts of the seed,
130    // and then hash, whereas always_hash hashes it first. I'll have
131    // to check this for compatibility with my Perl/C implementations.
132    fn next_u32(&mut self) -> u32 {
133	let mut val : u32;
134	loop {
135	    if self.avail_bytes < 4 || self.always_hash {
136		self.next_hash();
137	    }
138	    // convert a (little endian) slice of state into a u32
139	    let p = self.state_bytes - self.avail_bytes;
140	    self.avail_bytes -= 4;
141	    let array = [self.state[p], self.state[p+1],
142			 self.state[p+2], self.state[p+3]];
143	    val = u32::from_le_bytes(array);
144            // eprintln!("RNG returning val {}", val);
145	    // half way towards making compatible with C/Perl versions
146	    if val != 0xffffffff { return val }
147	}
148    }
149
150    fn next_u64(&mut self) -> u64 {
151        impls::next_u64_via_u32(self)
152        // ah... the following ends up returning 0 when calling
153        // gen_range(), no doubt due to it looking at the high bytes
154        // or something stupid like that.
155        //
156        // self.next_u32() as u64
157    }
158
159    fn fill_bytes(&mut self, dest: &mut [u8]) {
160        impls::fill_bytes_via_next(self, dest)
161    }
162
163    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
164	Ok(self.fill_bytes(dest))
165    }
166}
167
168
169// Implement a Distribution 
170use rand::distributions::Distribution;
171
172/// Usage
173///
174/// ```rust
175///
176/// use rand::distributions::Distribution;
177/// use rand::{Rng, SeedableRng};
178///
179/// use onlinecode::rng::{SHA1Rng, Compatible};
180///
181/// let zeros = [0u8; 20];
182/// let mut rng = SHA1Rng::from_seed(zeros).always_hash();
183///
184/// let mut pick = rng.sample(Compatible {});
185///
186/// println!("Got value {0:.20} from rng", pick);
187/// assert!(pick >= 0.0);
188/// assert!(pick < 1.0);
189///
190/// // Alternative way of calling:
191/// let distrib = Compatible {};
192/// pick = distrib.sample(&mut rng);
193///
194/// ```
195pub struct Compatible { }
196
197impl Distribution<f64> for Compatible {
198
199    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
200	// The normal Standard lops off some low bits. Here, I use a
201	// full 32-bit rand, convert it to an f64
202	let mut val : u32 = rng.gen();
203	if val == u32::MAX {
204	    // this is the least bad way of dealing with the
205	    // possibility that the RNG sends us 0xffffffff.  It
206	    // shouldn't, so a panic might be more in order, but I
207	    // don't want (literally) random panics in the code.
208	    eprintln!("Random number error; \"fixing\" it");
209	    val = rng.gen();
210	    // TODO: find a seed that should generate 0xffffffff as
211	    // its random output. Use that as a test case.
212	}
213	let double : f64 = val.into();
214	double / (u32::MAX as f64)
215    }
216}
217
218#[cfg(test)]
219
220mod tests {
221
222    use rand::SeedableRng;
223    use rand::Rng;
224    use crate::rng::SHA1Rng;
225
226    #[test]
227    fn all_zeros_array() {
228	let zeros = [0u8; 20];
229	let rng = SHA1Rng::from_seed(zeros);
230	assert_eq!(rng.always_hash, true);
231    }
232
233    #[test]
234    fn build_always_hash() {
235	let zeros = [0u8; 20];
236	let rng = SHA1Rng::from_seed(zeros).always_hash();
237	assert_eq!(rng.always_hash, true);
238    }
239
240    #[test]
241    fn build_never_hash() {
242	let zeros = [0u8; 20];
243	let rng = SHA1Rng::from_seed(zeros).never_hash();
244	assert_eq!(rng.always_hash, false);
245    }
246
247    #[test]
248    fn get_some_zeros() {
249	let zeros = [0u8; 20];
250	let mut rng = SHA1Rng::from_seed(zeros).never_hash();
251
252	// should we be able to get 5 zeros before re-hashing?
253	let mut val : u32 = rng.gen();
254	assert_eq!(val, 0, "rand 0 == 0");
255	val = rng.gen();
256	assert_eq!(val, 0, "rand 1 == 0");
257	val = rng.gen();
258	assert_eq!(val, 0, "rand 2 == 0");
259	val = rng.gen();
260	assert_eq!(val, 0, "rand 3 == 0");
261	val = rng.gen();
262	assert_eq!(val, 0, "rand 4 == 0");
263	// don't know for certain that next value != 0, but good chance
264	val = rng.gen();
265	assert_ne!(val, 0, "rand 5 != 0");
266	
267    }
268
269    #[test]
270    fn get_single_zero() {
271	let zeros = [0u8; 20];
272	let mut rng = SHA1Rng::from_seed(zeros).always_hash();
273
274	// should we be able to get 1 zero before re-hashing? No!
275	// Don't know for certain that value != 0, but good chance
276	let mut val : u32 = rng.gen();
277	assert_ne!(val, 0, "always hash first value != 0");
278    }
279
280
281    
282    
283}