From 3f4265c2532f86e8a109d83539b4c1cb0f76a221 Mon Sep 17 00:00:00 2001 From: Fethbita Date: Fri, 4 Oct 2024 23:50:14 +0300 Subject: [PATCH 1/3] Add a new page that explains testing functions that use random generator Give an example showing how to make a MockRng struct and use it instead, providing a default RNG as well --- src/SUMMARY.md | 1 + src/guide-test-fn-rng.md | 105 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 src/guide-test-fn-rng.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 25ed260..b21c3be 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -21,6 +21,7 @@ - [Random processess](guide-process.md) - [Sequences](guide-seq.md) - [Error handling](guide-err.md) + - [Testing functions that use RNGs](guide-test-fn-rng.md) - [Updating](update.md) - [Updating to 0.5](update-0.5.md) diff --git a/src/guide-test-fn-rng.md b/src/guide-test-fn-rng.md new file mode 100644 index 0000000..d0190a5 --- /dev/null +++ b/src/guide-test-fn-rng.md @@ -0,0 +1,105 @@ +# Testing functions that use RNGs + +Occasionally a function that uses random number generators might need to be tested. For functions that need to be tested with test vectors, the following approach might be adapted: + +```rust +use rand::{RngCore, CryptoRng, rngs::OsRng}; + +pub struct CryptoOperations { + rng: R +} + +impl CryptoOperations { + #[must_use] + pub fn new() -> Self { + Self { + rng: R::default() + } + } + + pub fn xor_with_random_bytes(&mut self, secret: &mut [u8; 8]) -> [u8; 8] { + let mut mask: [u8; 8] = [0; 8]; + self.rng.fill_bytes(&mut mask); + + for (byte, mask_byte) in secret.iter_mut().zip(mask.iter()) { + *byte ^= mask_byte; + } + + mask + } +} + +fn main() { + let mut crypto_ops = ::new(); + + let mut secret: [u8; 8] = *b"\x00\x01\x02\x03\x04\x05\x06\x07"; + let mask = crypto_ops.xor_with_random_bytes(&mut secret); + + println!("Modified Secret (XORed): {:?}", secret); + println!("Mask: {:?}", mask); +} +``` + +And as for tests, we can create a MockRng that implements RngCore and CryptoRng and provide a Default implementation with the value we want to return. Notice that the MocRng struct is marked with `#[cfg(test)]` as to not allow tainting the valid usages of the `xor_with_random_bytes` function: + +```rust +#[cfg(test)] +#[derive(Clone, Copy, Debug)] +struct MockRng { + data: [u8; 8], + index: usize, +} + +#[cfg(test)] +impl Default for MockRng { + fn default() -> MockRng { + MockRng { + data: *b"\x57\x88\x1e\xed\x1c\x72\x01\xd8", + index: 0, + } + } +} + +#[cfg(test)] +impl CryptoRng for MockRng {} + +#[cfg(test)] +impl RngCore for MockRng { + fn next_u32(&mut self) -> u32 { + unimplemented!() + } + + fn next_u64(&mut self) -> u64 { + unimplemented!() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + for byte in dest.iter_mut() { + *byte = self.data[self.index]; + self.index = (self.index + 1) % self.data.len(); + } + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { + unimplemented!() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_xor_with_mock_rng() { + let mut crypto_ops = CryptoOperations::::new(); + let mut secret: [u8; 8] = *b"\x01\x01\x02\x03\x04\x05\x06\x07"; + + let mask = crypto_ops.xor_with_random_bytes(&mut secret); + let expected_mask = *b"\x57\x88\x1e\xed\x1c\x72\x01\xd8"; + let expected_xored_secret = *b"\x57\x89\x1c\xee\x18\x77\x07\xdf"; + + assert_eq!(secret, expected_xored_secret); + assert_eq!(mask, expected_mask); + } +} +``` \ No newline at end of file From 68e616fcc457c1e6a43236050a3911fbab08289e Mon Sep 17 00:00:00 2001 From: Fethbita Date: Sat, 5 Oct 2024 00:10:57 +0300 Subject: [PATCH 2/3] Simplify test code --- src/guide-test-fn-rng.md | 66 +++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/src/guide-test-fn-rng.md b/src/guide-test-fn-rng.md index d0190a5..dd4f482 100644 --- a/src/guide-test-fn-rng.md +++ b/src/guide-test-fn-rng.md @@ -40,54 +40,50 @@ fn main() { } ``` -And as for tests, we can create a MockRng that implements RngCore and CryptoRng and provide a Default implementation with the value we want to return. Notice that the MocRng struct is marked with `#[cfg(test)]` as to not allow tainting the valid usages of the `xor_with_random_bytes` function: +And as for tests, we can create a MockRng that implements RngCore and CryptoRng and provide a Default implementation with the value we want to return: ```rust #[cfg(test)] -#[derive(Clone, Copy, Debug)] -struct MockRng { - data: [u8; 8], - index: usize, -} +mod tests { + use super::*; -#[cfg(test)] -impl Default for MockRng { - fn default() -> MockRng { - MockRng { - data: *b"\x57\x88\x1e\xed\x1c\x72\x01\xd8", - index: 0, + #[derive(Clone, Copy, Debug)] + struct MockRng { + data: [u8; 8], + index: usize, + } + + impl Default for MockRng { + fn default() -> MockRng { + MockRng { + data: *b"\x57\x88\x1e\xed\x1c\x72\x01\xd8", + index: 0, + } } } -} -#[cfg(test)] -impl CryptoRng for MockRng {} + impl CryptoRng for MockRng {} -#[cfg(test)] -impl RngCore for MockRng { - fn next_u32(&mut self) -> u32 { - unimplemented!() - } + impl RngCore for MockRng { + fn next_u32(&mut self) -> u32 { + unimplemented!() + } - fn next_u64(&mut self) -> u64 { - unimplemented!() - } + fn next_u64(&mut self) -> u64 { + unimplemented!() + } - fn fill_bytes(&mut self, dest: &mut [u8]) { - for byte in dest.iter_mut() { - *byte = self.data[self.index]; - self.index = (self.index + 1) % self.data.len(); + fn fill_bytes(&mut self, dest: &mut [u8]) { + for byte in dest.iter_mut() { + *byte = self.data[self.index]; + self.index = (self.index + 1) % self.data.len(); + } } - } - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { - unimplemented!() + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { + unimplemented!() + } } -} - -#[cfg(test)] -mod tests { - use super::*; #[test] fn test_xor_with_mock_rng() { From 9212e085eb151c25445979dfe643dd8ca9486512 Mon Sep 17 00:00:00 2001 From: Fethbita Date: Mon, 7 Oct 2024 18:59:05 +0300 Subject: [PATCH 3/3] Fix according to feedback --- src/guide-test-fn-rng.md | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/guide-test-fn-rng.md b/src/guide-test-fn-rng.md index dd4f482..146f533 100644 --- a/src/guide-test-fn-rng.md +++ b/src/guide-test-fn-rng.md @@ -5,20 +5,20 @@ Occasionally a function that uses random number generators might need to be test ```rust use rand::{RngCore, CryptoRng, rngs::OsRng}; -pub struct CryptoOperations { +pub struct CryptoOperations { rng: R } -impl CryptoOperations { +impl CryptoOperations { #[must_use] - pub fn new() -> Self { + pub fn new(rng: R) -> Self { Self { - rng: R::default() + rng } } pub fn xor_with_random_bytes(&mut self, secret: &mut [u8; 8]) -> [u8; 8] { - let mut mask: [u8; 8] = [0; 8]; + let mut mask = [0u8; 8]; self.rng.fill_bytes(&mut mask); for (byte, mask_byte) in secret.iter_mut().zip(mask.iter()) { @@ -30,7 +30,8 @@ impl CryptoOperations { } fn main() { - let mut crypto_ops = ::new(); + let rng = OsRng; + let mut crypto_ops = ::new(rng); let mut secret: [u8; 8] = *b"\x00\x01\x02\x03\x04\x05\x06\x07"; let mask = crypto_ops.xor_with_random_bytes(&mut secret); @@ -40,7 +41,7 @@ fn main() { } ``` -And as for tests, we can create a MockRng that implements RngCore and CryptoRng and provide a Default implementation with the value we want to return: +To test this, we can create a `MockCryptoRng` implementing `RngCore` and `CryptoRng` in our testing module. Note that `MockCryptoRng` is private and `#[cfg(test)] mod tests` is cfg-gated to our test environment, thus ensuring that `MockCryptoRng` cannot accidentally be used in production. ```rust #[cfg(test)] @@ -48,23 +49,23 @@ mod tests { use super::*; #[derive(Clone, Copy, Debug)] - struct MockRng { + struct MockCryptoRng { data: [u8; 8], index: usize, } - impl Default for MockRng { - fn default() -> MockRng { - MockRng { - data: *b"\x57\x88\x1e\xed\x1c\x72\x01\xd8", + impl MockCryptoRng { + fn new(data: [u8; 8]) -> MockCryptoRng { + MockCryptoRng { + data, index: 0, } } } - impl CryptoRng for MockRng {} + impl CryptoRng for MockCryptoRng {} - impl RngCore for MockRng { + impl RngCore for MockCryptoRng { fn next_u32(&mut self) -> u32 { unimplemented!() } @@ -87,9 +88,10 @@ mod tests { #[test] fn test_xor_with_mock_rng() { - let mut crypto_ops = CryptoOperations::::new(); - let mut secret: [u8; 8] = *b"\x01\x01\x02\x03\x04\x05\x06\x07"; + let mock_crypto_rng = MockCryptoRng::new(*b"\x57\x88\x1e\xed\x1c\x72\x01\xd8"); + let mut crypto_ops = CryptoOperations::new(mock_crypto_rng); + let mut secret: [u8; 8] = *b"\x00\x01\x02\x03\x04\x05\x06\x07"; let mask = crypto_ops.xor_with_random_bytes(&mut secret); let expected_mask = *b"\x57\x88\x1e\xed\x1c\x72\x01\xd8"; let expected_xored_secret = *b"\x57\x89\x1c\xee\x18\x77\x07\xdf";