Skip to main content

steam_client/utils/
rng.rs

1//! Random number generator abstraction for testable randomness.
2//!
3//! This module provides an `Rng` trait that abstracts random number generation,
4//! enabling deterministic testing of code that uses randomness.
5//!
6//! # Example
7//!
8//! ```rust
9//! use steam_client::utils::rng::{MockRng, Rng, ThreadRng};
10//!
11//! // Production: use ThreadRng
12//! let rng = ThreadRng;
13//! let value = rng.gen_usize(100);
14//!
15//! // Testing: use MockRng for deterministic tests
16//! let mock = MockRng::new();
17//! mock.set_usize(42);
18//! assert_eq!(mock.gen_usize(100), 42);
19//! ```
20
21use parking_lot::Mutex;
22use rand::Rng as _;
23
24/// Trait for generating random numbers.
25///
26/// This trait abstracts random number generation, allowing mock implementations
27/// for deterministic testing of randomness-dependent logic.
28pub trait Rng: Send + Sync {
29    /// Generate a random usize in the range [0, max).
30    fn gen_usize(&self, max: usize) -> usize;
31
32    /// Generate a random i32.
33    fn gen_i32(&self) -> i32;
34
35    /// Generate a random u32.
36    fn gen_u32(&self) -> u32;
37
38    /// Generate a random u64.
39    fn gen_u64(&self) -> u64;
40
41    /// Generate a vector of random bytes of the specified length.
42    fn gen_bytes(&self, len: usize) -> Vec<u8>;
43}
44
45/// Thread-local random number generator using the system RNG.
46///
47/// This is the default RNG used in production code.
48#[derive(Debug, Clone, Copy, Default)]
49pub struct ThreadRng;
50
51impl Rng for ThreadRng {
52    fn gen_usize(&self, max: usize) -> usize {
53        if max == 0 {
54            return 0;
55        }
56        rand::rng().random_range(0..max)
57    }
58
59    fn gen_i32(&self) -> i32 {
60        rand::rng().random::<i32>()
61    }
62
63    fn gen_u32(&self) -> u32 {
64        rand::rng().random::<u32>()
65    }
66
67    fn gen_u64(&self) -> u64 {
68        rand::rng().random::<u64>()
69    }
70
71    fn gen_bytes(&self, len: usize) -> Vec<u8> {
72        let mut bytes = vec![0u8; len];
73        rand::RngCore::fill_bytes(&mut rand::rng(), &mut bytes[..]);
74        bytes
75    }
76}
77
78impl steam_cm_provider::CmRng for ThreadRng {
79    fn gen_u32(&self) -> u32 {
80        rand::rng().random::<u32>()
81    }
82
83    fn gen_usize(&self, max: usize) -> usize {
84        if max == 0 {
85            return 0;
86        }
87        rand::rng().random_range(0..max)
88    }
89}
90
91/// Mock random number generator for testing.
92///
93/// Allows tests to control exactly what values are returned by RNG calls.
94///
95/// # Example
96///
97/// ```rust
98/// use steam_client::utils::rng::{MockRng, Rng};
99///
100/// let mock = MockRng::new();
101///
102/// // Set specific values to be returned
103/// mock.set_usize(5);
104/// assert_eq!(mock.gen_usize(100), 5);
105///
106/// // Values can be changed at any time
107/// mock.set_usize(10);
108/// assert_eq!(mock.gen_usize(100), 10);
109/// ```
110#[derive(Debug)]
111pub struct MockRng {
112    usize_value: Mutex<usize>,
113    i32_value: Mutex<i32>,
114    u32_value: Mutex<u32>,
115    u64_value: Mutex<u64>,
116    bytes_value: Mutex<Vec<u8>>,
117}
118
119impl MockRng {
120    /// Create a new mock RNG with default values (all zeros).
121    pub fn new() -> Self {
122        Self {
123            usize_value: Mutex::new(0),
124            i32_value: Mutex::new(0),
125            u32_value: Mutex::new(0),
126            u64_value: Mutex::new(0),
127            bytes_value: Mutex::new(Vec::new()),
128        }
129    }
130
131    /// Create a new mock RNG with specific initial values.
132    pub fn with_values(usize_val: usize, i32_val: i32, u32_val: u32) -> Self {
133        Self::with_all_values(usize_val, i32_val, u32_val, 0, Vec::new())
134    }
135
136    /// Create a new mock RNG with all initial values including u64 and bytes.
137    pub fn with_all_values(usize_val: usize, i32_val: i32, u32_val: u32, u64_val: u64, bytes_val: Vec<u8>) -> Self {
138        Self {
139            usize_value: Mutex::new(usize_val),
140            i32_value: Mutex::new(i32_val),
141            u32_value: Mutex::new(u32_val),
142            u64_value: Mutex::new(u64_val),
143            bytes_value: Mutex::new(bytes_val),
144        }
145    }
146
147    /// Set the value to be returned by `gen_usize`.
148    pub fn set_usize(&self, value: usize) {
149        *self.usize_value.lock() = value;
150    }
151
152    /// Set the value to be returned by `gen_i32`.
153    pub fn set_i32(&self, value: i32) {
154        *self.i32_value.lock() = value;
155    }
156
157    /// Set the value to be returned by `gen_u32`.
158    pub fn set_u32(&self, value: u32) {
159        *self.u32_value.lock() = value;
160    }
161
162    /// Set the value to be returned by `gen_u64`.
163    pub fn set_u64(&self, value: u64) {
164        *self.u64_value.lock() = value;
165    }
166
167    /// Set the value to be returned by `gen_bytes`.
168    pub fn set_bytes(&self, value: Vec<u8>) {
169        *self.bytes_value.lock() = value;
170    }
171
172    /// Get the current usize value.
173    pub fn current_usize(&self) -> usize {
174        *self.usize_value.lock()
175    }
176
177    /// Get the current i32 value.
178    pub fn current_i32(&self) -> i32 {
179        *self.i32_value.lock()
180    }
181
182    /// Get the current u32 value.
183    pub fn current_u32(&self) -> u32 {
184        *self.u32_value.lock()
185    }
186
187    /// Get the current u64 value.
188    pub fn current_u64(&self) -> u64 {
189        *self.u64_value.lock()
190    }
191
192    /// Get the current bytes value.
193    pub fn current_bytes(&self) -> Vec<u8> {
194        self.bytes_value.lock().clone()
195    }
196}
197
198impl Default for MockRng {
199    fn default() -> Self {
200        Self::new()
201    }
202}
203
204impl Rng for MockRng {
205    fn gen_usize(&self, _max: usize) -> usize {
206        *self.usize_value.lock()
207    }
208
209    fn gen_i32(&self) -> i32 {
210        *self.i32_value.lock()
211    }
212
213    fn gen_u32(&self) -> u32 {
214        *self.u32_value.lock()
215    }
216
217    fn gen_u64(&self) -> u64 {
218        *self.u64_value.lock()
219    }
220
221    fn gen_bytes(&self, len: usize) -> Vec<u8> {
222        let val = self.bytes_value.lock();
223        if val.is_empty() {
224            vec![0; len]
225        } else {
226            // Return stored bytes, truncated or padded to requested length
227            let mut result = val.clone();
228            result.resize(len, 0);
229            result
230        }
231    }
232}
233
234impl steam_cm_provider::CmRng for MockRng {
235    fn gen_u32(&self) -> u32 {
236        *self.u32_value.lock()
237    }
238
239    fn gen_usize(&self, _max: usize) -> usize {
240        *self.usize_value.lock()
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247
248    #[test]
249    fn test_thread_rng_gen_usize() {
250        let rng = ThreadRng;
251        // Just verify it doesn't panic and returns valid range
252        for _ in 0..100 {
253            let value = rng.gen_usize(10);
254            assert!(value < 10);
255        }
256    }
257
258    #[test]
259    fn test_thread_rng_gen_usize_zero_max() {
260        let rng = ThreadRng;
261        assert_eq!(rng.gen_usize(0), 0);
262    }
263
264    #[test]
265    fn test_mock_rng_default_values() {
266        let mock = MockRng::new();
267        assert_eq!(mock.gen_usize(100), 0);
268        assert_eq!(mock.gen_i32(), 0);
269        assert_eq!(mock.gen_u32(), 0);
270    }
271
272    #[test]
273    fn test_mock_rng_with_values() {
274        let mock = MockRng::with_values(42, -123, 456);
275        assert_eq!(mock.gen_usize(100), 42);
276        assert_eq!(mock.gen_i32(), -123);
277        assert_eq!(mock.gen_u32(), 456);
278    }
279
280    #[test]
281    fn test_mock_rng_set_values() {
282        let mock = MockRng::new();
283
284        mock.set_usize(100);
285        assert_eq!(mock.gen_usize(1000), 100);
286
287        mock.set_i32(-999);
288        assert_eq!(mock.gen_i32(), -999);
289
290        mock.set_u32(12345);
291        assert_eq!(mock.gen_u32(), 12345);
292    }
293
294    #[test]
295    fn test_mock_rng_ignores_max() {
296        let mock = MockRng::new();
297        mock.set_usize(50);
298
299        // MockRng ignores the max parameter - it's the caller's responsibility
300        // to ensure test values are valid for the use case
301        assert_eq!(mock.gen_usize(10), 50);
302        assert_eq!(mock.gen_usize(100), 50);
303        assert_eq!(mock.gen_usize(1000), 50);
304    }
305
306    #[test]
307    fn test_mock_rng_current_values() {
308        let mock = MockRng::with_values(1, 2, 3);
309
310        assert_eq!(mock.current_usize(), 1);
311        assert_eq!(mock.current_i32(), 2);
312        assert_eq!(mock.current_u32(), 3);
313
314        mock.set_usize(10);
315        assert_eq!(mock.current_usize(), 10);
316    }
317
318    #[test]
319    fn test_thread_rng_gen_bytes() {
320        let rng = ThreadRng;
321        let bytes = rng.gen_bytes(10);
322        assert_eq!(bytes.len(), 10);
323    }
324
325    #[test]
326    fn test_mock_rng_bytes() {
327        let mock = MockRng::new();
328
329        // Default should be zeros
330        let bytes = mock.gen_bytes(5);
331        assert_eq!(bytes, vec![0, 0, 0, 0, 0]);
332
333        // Set bytes
334        let test_bytes = vec![1, 2, 3, 4, 5];
335        mock.set_bytes(test_bytes.clone());
336        assert_eq!(mock.gen_bytes(5), test_bytes);
337        assert_eq!(mock.current_bytes(), test_bytes);
338
339        // Test resizing behavior
340        assert_eq!(mock.gen_bytes(3), vec![1, 2, 3]);
341        assert_eq!(mock.gen_bytes(7), vec![1, 2, 3, 4, 5, 0, 0]);
342    }
343}