Monorepo for Tangled tangled.org
5

Configure Feed

Select the types of activity you want to include in your feed.

1use std::sync::atomic::{AtomicU64, Ordering}; 2 3pub trait Entropy: Send + Sync + 'static { 4 fn next_u64(&self) -> u64; 5} 6 7#[derive(Clone, Copy, Debug, Default)] 8pub struct OsEntropy; 9 10impl Entropy for OsEntropy { 11 fn next_u64(&self) -> u64 { 12 let mut buf = [0u8; 8]; 13 getrandom::fill(&mut buf).expect("os entropy source unavailable"); 14 u64::from_le_bytes(buf) 15 } 16} 17 18#[derive(Debug)] 19pub struct SeededEntropy { 20 state: AtomicU64, 21} 22 23impl SeededEntropy { 24 const GOLDEN: u64 = 0x9E37_79B9_7F4A_7C15; 25 26 pub fn new(seed: u64) -> Self { 27 Self { 28 state: AtomicU64::new(seed), 29 } 30 } 31} 32 33impl Entropy for SeededEntropy { 34 fn next_u64(&self) -> u64 { 35 let prev = self.state.fetch_add(Self::GOLDEN, Ordering::Relaxed); 36 let z = prev.wrapping_add(Self::GOLDEN); 37 let z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); 38 let z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); 39 z ^ (z >> 31) 40 } 41} 42 43#[cfg(test)] 44mod tests { 45 use super::*; 46 use std::sync::Arc; 47 use std::sync::atomic::{AtomicU64, Ordering}; 48 49 struct CountingEntropy(AtomicU64); 50 51 impl Entropy for CountingEntropy { 52 fn next_u64(&self) -> u64 { 53 self.0.fetch_add(1, Ordering::SeqCst) 54 } 55 } 56 57 #[test] 58 fn external_impl_works_behind_dyn_arc() { 59 let e: Arc<dyn Entropy> = Arc::new(CountingEntropy(AtomicU64::new(100))); 60 assert_eq!(e.next_u64(), 100); 61 assert_eq!(e.next_u64(), 101); 62 } 63 64 #[test] 65 fn seeded_entropy_two_constructions_with_same_seed_yield_same_stream() { 66 let a = SeededEntropy::new(0xCAFE_F00D); 67 let b = SeededEntropy::new(0xCAFE_F00D); 68 for _ in 0..32 { 69 assert_eq!( 70 a.next_u64(), 71 b.next_u64(), 72 "splitmix stream must reproduce exactly across constructions with same seed", 73 ); 74 } 75 } 76 77 #[test] 78 fn seeded_entropy_different_seeds_diverge() { 79 let a = SeededEntropy::new(1); 80 let b = SeededEntropy::new(2); 81 let mut all_match = true; 82 for _ in 0..32 { 83 if a.next_u64() != b.next_u64() { 84 all_match = false; 85 break; 86 } 87 } 88 assert!( 89 !all_match, 90 "two seeded entropies with distinct seeds must diverge inside 32 draws", 91 ); 92 } 93 94 #[test] 95 fn seeded_entropy_does_not_repeat_inside_short_window() { 96 let e = SeededEntropy::new(0); 97 let mut seen = std::collections::HashSet::new(); 98 for _ in 0..1024 { 99 assert!( 100 seen.insert(e.next_u64()), 101 "splitmix collided inside 1024 draws" 102 ); 103 } 104 } 105 106 #[test] 107 fn os_entropy_does_not_collide_on_back_to_back_calls() { 108 let e = OsEntropy; 109 let a = e.next_u64(); 110 let b = e.next_u64(); 111 assert_ne!( 112 a, b, 113 "back-to-back os entropy collided, getrandom likely not actually wired up", 114 ); 115 } 116}