From 672d69f14cf9391516d8638d200fdc4aae0b5dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Mon, 25 Nov 2024 19:17:54 +0300 Subject: [PATCH] Add functions to generate random `u32` and `u64` values --- src/backends.rs | 16 ++++- src/backends/apple_other.rs | 2 + src/backends/custom.rs | 2 + src/backends/esp_idf.rs | 2 + src/backends/fuchsia.rs | 2 + src/backends/getentropy.rs | 2 + src/backends/getrandom.rs | 2 + src/backends/hermit.rs | 4 ++ src/backends/linux_android.rs | 2 + src/backends/linux_android_with_fallback.rs | 2 + src/backends/linux_rustix.rs | 2 + src/backends/netbsd.rs | 2 + src/backends/rdrand.rs | 61 ++++++++++++++--- src/backends/rndr.rs | 25 ++++++- src/backends/solaris.rs | 2 + src/backends/solid.rs | 2 + src/backends/use_file.rs | 3 + src/backends/vxworks.rs | 2 + src/backends/wasi.rs | 73 --------------------- src/backends/wasi_p1.rs | 30 +++++++++ src/backends/wasi_p2.rs | 47 +++++++++++++ src/backends/wasm_js.rs | 2 + src/backends/windows.rs | 2 + src/backends/windows7.rs | 2 + src/lib.rs | 28 ++++++++ src/util.rs | 36 +++++++++- tests/mod.rs | 67 +++++++++++++++++-- 27 files changed, 331 insertions(+), 91 deletions(-) delete mode 100644 src/backends/wasi.rs create mode 100644 src/backends/wasi_p1.rs create mode 100644 src/backends/wasi_p2.rs diff --git a/src/backends.rs b/src/backends.rs index 7ed94cf3..d5164173 100644 --- a/src/backends.rs +++ b/src/backends.rs @@ -116,8 +116,20 @@ cfg_if! { mod apple_other; pub use apple_other::*; } else if #[cfg(all(target_arch = "wasm32", target_os = "wasi"))] { - mod wasi; - pub use wasi::*; + cfg_if! { + if #[cfg(target_env = "p1")] { + mod wasi_p1; + pub use wasi_p1::*; + } else if #[cfg(target_env = "p2")] { + mod wasi_p2; + pub use wasi_p2::*; + } else { + compile_error!( + "Unknown version of WASI (only previews 1 and 2 are supported) \ + or Rust version older than 1.80 was used" + ); + } + } } else if #[cfg(target_os = "hermit")] { mod hermit; pub use hermit::*; diff --git a/src/backends/apple_other.rs b/src/backends/apple_other.rs index 1990523f..127a31e3 100644 --- a/src/backends/apple_other.rs +++ b/src/backends/apple_other.rs @@ -2,6 +2,8 @@ use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; +pub use crate::util::{inner_u32, inner_u64}; + pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { let dst_ptr = dest.as_mut_ptr().cast::(); let ret = unsafe { libc::CCRandomGenerateBytes(dst_ptr, dest.len()) }; diff --git a/src/backends/custom.rs b/src/backends/custom.rs index e97d3293..0c482946 100644 --- a/src/backends/custom.rs +++ b/src/backends/custom.rs @@ -2,6 +2,8 @@ use crate::Error; use core::mem::MaybeUninit; +pub use crate::util::{inner_u32, inner_u64}; + pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { extern "Rust" { fn __getrandom_v03_custom(dest: *mut u8, len: usize) -> Result<(), Error>; diff --git a/src/backends/esp_idf.rs b/src/backends/esp_idf.rs index 2df7b9aa..0f0ff7f4 100644 --- a/src/backends/esp_idf.rs +++ b/src/backends/esp_idf.rs @@ -2,6 +2,8 @@ use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; +pub use crate::util::{inner_u32, inner_u64}; + #[cfg(not(target_os = "espidf"))] compile_error!("`esp_idf` backend can be enabled only for ESP-IDF targets!"); diff --git a/src/backends/fuchsia.rs b/src/backends/fuchsia.rs index b8e6de86..5edd210d 100644 --- a/src/backends/fuchsia.rs +++ b/src/backends/fuchsia.rs @@ -2,6 +2,8 @@ use crate::Error; use core::mem::MaybeUninit; +pub use crate::util::{inner_u32, inner_u64}; + #[link(name = "zircon")] extern "C" { fn zx_cprng_draw(buffer: *mut u8, length: usize); diff --git a/src/backends/getentropy.rs b/src/backends/getentropy.rs index c93c6ccf..e0b2d34c 100644 --- a/src/backends/getentropy.rs +++ b/src/backends/getentropy.rs @@ -10,6 +10,8 @@ use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; +pub use crate::util::{inner_u32, inner_u64}; + #[path = "../util_libc.rs"] mod util_libc; diff --git a/src/backends/getrandom.rs b/src/backends/getrandom.rs index 298059c2..a00829f7 100644 --- a/src/backends/getrandom.rs +++ b/src/backends/getrandom.rs @@ -18,6 +18,8 @@ use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; +pub use crate::util::{inner_u32, inner_u64}; + #[path = "../util_libc.rs"] mod util_libc; diff --git a/src/backends/hermit.rs b/src/backends/hermit.rs index 594b330b..13cb2fd4 100644 --- a/src/backends/hermit.rs +++ b/src/backends/hermit.rs @@ -2,6 +2,10 @@ use crate::Error; use core::mem::MaybeUninit; +// Note that `sys_secure_rand32` and `sys_secure_rand64` are implemented using `sys_read_entropy`: +// https://docs.rs/libhermit-rs/0.6.3/src/hermit/syscalls/entropy.rs.html#62-97 +pub use crate::util::{inner_u32, inner_u64}; + extern "C" { fn sys_read_entropy(buffer: *mut u8, length: usize, flags: u32) -> isize; } diff --git a/src/backends/linux_android.rs b/src/backends/linux_android.rs index cc59b0b1..6c0b66ae 100644 --- a/src/backends/linux_android.rs +++ b/src/backends/linux_android.rs @@ -2,6 +2,8 @@ use crate::Error; use core::mem::MaybeUninit; +pub use crate::util::{inner_u32, inner_u64}; + #[path = "../util_libc.rs"] mod util_libc; diff --git a/src/backends/linux_android_with_fallback.rs b/src/backends/linux_android_with_fallback.rs index 4e3ea98c..cdfff980 100644 --- a/src/backends/linux_android_with_fallback.rs +++ b/src/backends/linux_android_with_fallback.rs @@ -9,6 +9,8 @@ use core::{ }; use use_file::util_libc; +pub use crate::util::{inner_u32, inner_u64}; + type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t; /// Sentinel value which indicates that `libc::getrandom` either not available, diff --git a/src/backends/linux_rustix.rs b/src/backends/linux_rustix.rs index 4fef3381..d3bcce3e 100644 --- a/src/backends/linux_rustix.rs +++ b/src/backends/linux_rustix.rs @@ -2,6 +2,8 @@ use crate::{Error, MaybeUninit}; use rustix::rand::{getrandom_uninit, GetRandomFlags}; +pub use crate::util::{inner_u32, inner_u64}; + #[cfg(not(any(target_os = "android", target_os = "linux")))] compile_error!("`linux_rustix` backend can be enabled only for Linux/Android targets!"); diff --git a/src/backends/netbsd.rs b/src/backends/netbsd.rs index ddb69736..0e3268ef 100644 --- a/src/backends/netbsd.rs +++ b/src/backends/netbsd.rs @@ -12,6 +12,8 @@ use core::{ sync::atomic::{AtomicPtr, Ordering}, }; +pub use crate::util::{inner_u32, inner_u64}; + #[path = "../util_libc.rs"] mod util_libc; diff --git a/src/backends/rdrand.rs b/src/backends/rdrand.rs index 0a278b9a..19fa97ba 100644 --- a/src/backends/rdrand.rs +++ b/src/backends/rdrand.rs @@ -20,6 +20,8 @@ cfg_if! { } } +static RDRAND_GOOD: lazy::LazyBool = lazy::LazyBool::new(); + // Recommendation from "Intel® Digital Random Number Generator (DRNG) Software // Implementation Guide" - Section 5.2.1 and "Intel® 64 and IA-32 Architectures // Software Developer’s Manual" - Volume 1 - Section 7.3.17.1. @@ -99,15 +101,6 @@ fn is_rdrand_good() -> bool { unsafe { self_test() } } -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - static RDRAND_GOOD: lazy::LazyBool = lazy::LazyBool::new(); - if !RDRAND_GOOD.unsync_init(is_rdrand_good) { - return Err(Error::NO_RDRAND); - } - // SAFETY: After this point, we know rdrand is supported. - unsafe { rdrand_exact(dest) }.ok_or(Error::FAILED_RDRAND) -} - // TODO: make this function safe when we have feature(target_feature_11) #[target_feature(enable = "rdrand")] unsafe fn rdrand_exact(dest: &mut [MaybeUninit]) -> Option<()> { @@ -127,3 +120,53 @@ unsafe fn rdrand_exact(dest: &mut [MaybeUninit]) -> Option<()> { } Some(()) } + +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "rdrand")] +unsafe fn rdrand_u32() -> Option { + rdrand().map(crate::util::truncate) +} + +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "rdrand")] +unsafe fn rdrand_u64() -> Option { + rdrand() +} + +#[cfg(target_arch = "x86")] +#[target_feature(enable = "rdrand")] +unsafe fn rdrand_u32() -> Option { + rdrand() +} + +#[cfg(target_arch = "x86")] +#[target_feature(enable = "rdrand")] +unsafe fn rdrand_u64() -> Option { + let a = rdrand()?; + let b = rdrand()?; + Some((u64::from(a) << 32) || u64::from(b)) +} + +pub fn inner_u32() -> Result { + if !RDRAND_GOOD.unsync_init(is_rdrand_good) { + return Err(Error::NO_RDRAND); + } + // SAFETY: After this point, we know rdrand is supported. + unsafe { rdrand_u32() }.ok_or(Error::FAILED_RDRAND) +} + +pub fn inner_u64() -> Result { + if !RDRAND_GOOD.unsync_init(is_rdrand_good) { + return Err(Error::NO_RDRAND); + } + // SAFETY: After this point, we know rdrand is supported. + unsafe { rdrand_u64() }.ok_or(Error::FAILED_RDRAND) +} + +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + if !RDRAND_GOOD.unsync_init(is_rdrand_good) { + return Err(Error::NO_RDRAND); + } + // SAFETY: After this point, we know rdrand is supported. + unsafe { rdrand_exact(dest) }.ok_or(Error::FAILED_RDRAND) +} diff --git a/src/backends/rndr.rs b/src/backends/rndr.rs index b6805b20..bd2878f0 100644 --- a/src/backends/rndr.rs +++ b/src/backends/rndr.rs @@ -2,7 +2,10 @@ //! //! Arm Architecture Reference Manual for A-profile architecture: //! ARM DDI 0487K.a, ID032224, D23.2.147 RNDR, Random Number -use crate::{util::slice_as_uninit, Error}; +use crate::{ + util::{slice_as_uninit, truncate}, + Error, +}; use core::arch::asm; use core::mem::{size_of, MaybeUninit}; @@ -101,6 +104,26 @@ fn is_rndr_available() -> bool { } } +pub fn inner_u32() -> Result { + if is_rndr_available() { + // SAFETY: after this point, we know the `rand` target feature is enabled + let res = unsafe { rndr() }; + res.map(truncate).ok_or(Error::RNDR_FAILURE) + } else { + Err(Error::RNDR_NOT_AVAILABLE) + } +} + +pub fn inner_u64() -> Result { + if is_rndr_available() { + // SAFETY: after this point, we know the `rand` target feature is enabled + let res = unsafe { rndr() }; + res.ok_or(Error::RNDR_FAILURE) + } else { + Err(Error::RNDR_NOT_AVAILABLE) + } +} + pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { if is_rndr_available() { // SAFETY: after this point, we know the `rand` target feature is enabled diff --git a/src/backends/solaris.rs b/src/backends/solaris.rs index a9a26804..ea5344fc 100644 --- a/src/backends/solaris.rs +++ b/src/backends/solaris.rs @@ -15,6 +15,8 @@ use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; +pub use crate::util::{inner_u32, inner_u64}; + #[path = "../util_libc.rs"] mod util_libc; diff --git a/src/backends/solid.rs b/src/backends/solid.rs index 50bbe47e..6699e686 100644 --- a/src/backends/solid.rs +++ b/src/backends/solid.rs @@ -2,6 +2,8 @@ use crate::Error; use core::mem::MaybeUninit; +pub use crate::util::{inner_u32, inner_u64}; + extern "C" { pub fn SOLID_RNG_SampleRandomBytes(buffer: *mut u8, length: usize) -> i32; } diff --git a/src/backends/use_file.rs b/src/backends/use_file.rs index 86dfb48c..ef12fca1 100644 --- a/src/backends/use_file.rs +++ b/src/backends/use_file.rs @@ -6,6 +6,9 @@ use core::{ sync::atomic::{AtomicI32, Ordering}, }; +#[cfg(not(any(target_os = "android", target_os = "linux")))] +pub use crate::util::{inner_u32, inner_u64}; + #[path = "../util_libc.rs"] pub(super) mod util_libc; diff --git a/src/backends/vxworks.rs b/src/backends/vxworks.rs index d595d17b..51b7580e 100644 --- a/src/backends/vxworks.rs +++ b/src/backends/vxworks.rs @@ -9,6 +9,8 @@ use core::{ #[path = "../util_libc.rs"] mod util_libc; +pub use crate::util::{inner_u32, inner_u64}; + pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { static RNG_INIT: AtomicBool = AtomicBool::new(false); while !RNG_INIT.load(Relaxed) { diff --git a/src/backends/wasi.rs b/src/backends/wasi.rs deleted file mode 100644 index a1ecf004..00000000 --- a/src/backends/wasi.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! Implementation for WASI (preview 1 and 2) -//! -//! `target_env = "p1"` was introduced only in Rust 1.80, so on earlier compiler versions this -//! code will result in a compilation error. -use crate::Error; -use core::mem::MaybeUninit; - -#[cfg(not(any(target_env = "p1", target_env = "p2")))] -compile_error!( - "Unknown version of WASI (only previews 1 and 2 are supported) \ - or Rust version older than 1.80 was used" -); - -#[cfg(target_env = "p1")] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - // This linking is vendored from the wasi crate: - // https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2344-2350 - #[link(wasm_import_module = "wasi_snapshot_preview1")] - extern "C" { - fn random_get(arg0: i32, arg1: i32) -> i32; - } - - // Based on the wasi code: - // https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2046-2062 - // Note that size of an allocated object can not be bigger than isize::MAX bytes. - // WASI 0.1 supports only 32-bit WASM, so casting length to `i32` is safe. - #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] - let ret = unsafe { random_get(dest.as_mut_ptr() as i32, dest.len() as i32) }; - match ret { - 0 => Ok(()), - code => { - let err = u32::try_from(code) - .map(Error::from_os_error) - .unwrap_or(Error::UNEXPECTED); - Err(err) - } - } -} - -#[cfg(target_env = "p2")] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - use core::ptr::copy_nonoverlapping; - use wasi::random::random::get_random_u64; - - let (prefix, chunks, suffix) = unsafe { dest.align_to_mut::>() }; - - // We use `get_random_u64` instead of `get_random_bytes` because the latter creates - // an allocation due to the Wit IDL [restrictions][0]. This should be fine since - // the main use case of `getrandom` is seed generation. - // - // [0]: https://github.com/WebAssembly/wasi-random/issues/27 - if !prefix.is_empty() { - let val = get_random_u64(); - let src = (&val as *const u64).cast(); - unsafe { - copy_nonoverlapping(src, prefix.as_mut_ptr(), prefix.len()); - } - } - - for dst in chunks { - dst.write(get_random_u64()); - } - - if !suffix.is_empty() { - let val = get_random_u64(); - let src = (&val as *const u64).cast(); - unsafe { - copy_nonoverlapping(src, suffix.as_mut_ptr(), suffix.len()); - } - } - - Ok(()) -} diff --git a/src/backends/wasi_p1.rs b/src/backends/wasi_p1.rs new file mode 100644 index 00000000..6eefdee6 --- /dev/null +++ b/src/backends/wasi_p1.rs @@ -0,0 +1,30 @@ +//! Implementation for WASI Preview 1 +use crate::Error; +use core::mem::MaybeUninit; + +pub use crate::util::{inner_u32, inner_u64}; + +// This linking is vendored from the wasi crate: +// https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2344-2350 +#[link(wasm_import_module = "wasi_snapshot_preview1")] +extern "C" { + fn random_get(arg0: i32, arg1: i32) -> i32; +} + +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Based on the wasi code: + // https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2046-2062 + // Note that size of an allocated object can not be bigger than isize::MAX bytes. + // WASI 0.1 supports only 32-bit WASM, so casting length to `i32` is safe. + #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] + let ret = unsafe { random_get(dest.as_mut_ptr() as i32, dest.len() as i32) }; + match ret { + 0 => Ok(()), + code => { + let err = u32::try_from(code) + .map(Error::from_os_error) + .unwrap_or(Error::UNEXPECTED); + Err(err) + } + } +} diff --git a/src/backends/wasi_p2.rs b/src/backends/wasi_p2.rs new file mode 100644 index 00000000..9d5d601d --- /dev/null +++ b/src/backends/wasi_p2.rs @@ -0,0 +1,47 @@ +//! Implementation for WASI Preview 2. +use crate::Error; +use core::mem::MaybeUninit; +use wasi::random::random::get_random_u64; + +pub fn inner_u32() -> Result { + let val = get_random_u64(); + Ok(crate::util::truncate(val)) +} + +pub fn inner_u64() -> Result { + Ok(get_random_u64()) +} + +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + use core::ptr::copy_nonoverlapping; + use wasi::random::random::get_random_u64; + + let (prefix, chunks, suffix) = unsafe { dest.align_to_mut::>() }; + + // We use `get_random_u64` instead of `get_random_bytes` because the latter creates + // an allocation due to the Wit IDL [restrictions][0]. This should be fine since + // the main use case of `getrandom` is seed generation. + // + // [0]: https://github.com/WebAssembly/wasi-random/issues/27 + if !prefix.is_empty() { + let val = get_random_u64(); + let src = (&val as *const u64).cast(); + unsafe { + copy_nonoverlapping(src, prefix.as_mut_ptr(), prefix.len()); + } + } + + for dst in chunks { + dst.write(get_random_u64()); + } + + if !suffix.is_empty() { + let val = get_random_u64(); + let src = (&val as *const u64).cast(); + unsafe { + copy_nonoverlapping(src, suffix.as_mut_ptr(), suffix.len()); + } + } + + Ok(()) +} diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 5cdca578..de2b4333 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -4,6 +4,8 @@ use crate::Error; extern crate std; use std::{mem::MaybeUninit, thread_local}; +pub use crate::util::{inner_u32, inner_u64}; + #[cfg(not(all( any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown", diff --git a/src/backends/windows.rs b/src/backends/windows.rs index 642206e7..6c8e46b1 100644 --- a/src/backends/windows.rs +++ b/src/backends/windows.rs @@ -23,6 +23,8 @@ use crate::Error; use core::mem::MaybeUninit; +pub use crate::util::{inner_u32, inner_u64}; + // Binding to the Windows.Win32.Security.Cryptography.ProcessPrng API. As // bcryptprimitives.dll lacks an import library, we use the windows-targets // crate to link to it. diff --git a/src/backends/windows7.rs b/src/backends/windows7.rs index 573ac881..2546719e 100644 --- a/src/backends/windows7.rs +++ b/src/backends/windows7.rs @@ -12,6 +12,8 @@ use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; +pub use crate::util::{inner_u32, inner_u64}; + // Binding to the Windows.Win32.Security.Authentication.Identity.RtlGenRandom // API. Don't use windows-targets as it doesn't support Windows 7 targets. #[link(name = "advapi32")] diff --git a/src/lib.rs b/src/lib.rs index b0034588..930b9452 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,3 +116,31 @@ pub fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error> { util::slice_assume_init_mut(dest) }) } + +/// Get random `u32` from the system's preferred random number source. +/// +/// # Examples +/// +/// ``` +/// # fn main() -> Result<(), getrandom::Error> { +/// let rng_seed = getrandom::u32()?; +/// # Ok(()) } +/// ``` +#[inline] +pub fn u32() -> Result { + backends::inner_u32() +} + +/// Get random `u64` from the system's preferred random number source. +/// +/// # Examples +/// +/// ``` +/// # fn main() -> Result<(), getrandom::Error> { +/// let rng_seed = getrandom::u64()?; +/// # Ok(()) } +/// ``` +#[inline] +pub fn u64() -> Result { + backends::inner_u64() +} diff --git a/src/util.rs b/src/util.rs index 9eeba9a7..4a55b0a5 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] -use core::{mem::MaybeUninit, ptr}; +use crate::Error; +use core::{mem::MaybeUninit, ptr, slice}; /// Polyfill for `maybe_uninit_slice` feature's /// `MaybeUninit::slice_assume_init_mut`. Every element of `slice` must have @@ -46,3 +47,36 @@ fn ptr_from_mut(r: &mut T) -> *mut T { fn ptr_from_ref(r: &T) -> *const T { r } + +/// Default implementation of `inner_u32` on top of `fill_uninit` +pub fn inner_u32() -> Result { + let mut res = MaybeUninit::::uninit(); + // SAFETY: the created slice has the same size as `res` + let dst = unsafe { + let p: *mut MaybeUninit = res.as_mut_ptr().cast(); + slice::from_raw_parts_mut(p, core::mem::size_of::()) + }; + crate::fill_uninit(dst)?; + // SAFETY: `dst` has been fully initialized by `imp::fill_inner` + // since it returned `Ok`. + Ok(unsafe { res.assume_init() }) +} + +/// Default implementation of `inner_u64` on top of `fill_uninit` +pub fn inner_u64() -> Result { + let mut res = MaybeUninit::::uninit(); + // SAFETY: the created slice has the same size as `res` + let dst = unsafe { + let p: *mut MaybeUninit = res.as_mut_ptr().cast(); + slice::from_raw_parts_mut(p, core::mem::size_of::()) + }; + crate::fill_uninit(dst)?; + // SAFETY: `dst` has been fully initialized by `imp::fill_inner` + // since it returned `Ok`. + Ok(unsafe { res.assume_init() }) +} + +/// Truncates `u64` and returns the lower 32 bits as `u32` +pub(crate) fn truncate(val: u64) -> u32 { + u32::try_from(val & u64::from(u32::MAX)).expect("The higher 32 bits are masked") +} diff --git a/tests/mod.rs b/tests/mod.rs index 58e22951..204ec653 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -14,13 +14,32 @@ fn test_zero() { assert!(res.is_empty()); } +trait DiffBits: Sized { + fn diff_bits(ab: (&Self, &Self)) -> usize; +} + +impl DiffBits for u8 { + fn diff_bits((a, b): (&Self, &Self)) -> usize { + (a ^ b).count_ones() as usize + } +} + +impl DiffBits for u32 { + fn diff_bits((a, b): (&Self, &Self)) -> usize { + (a ^ b).count_ones() as usize + } +} + +impl DiffBits for u64 { + fn diff_bits((a, b): (&Self, &Self)) -> usize { + (a ^ b).count_ones() as usize + } +} + // Return the number of bits in which s1 and s2 differ -fn num_diff_bits(s1: &[u8], s2: &[u8]) -> usize { +fn num_diff_bits(s1: &[T], s2: &[T]) -> usize { assert_eq!(s1.len(), s2.len()); - s1.iter() - .zip(s2.iter()) - .map(|(a, b)| (a ^ b).count_ones() as usize) - .sum() + s1.iter().zip(s2.iter()).map(T::diff_bits).sum() } // TODO: use `[const { MaybeUninit::uninit() }; N]` after MSRV is bumped to 1.79+ @@ -55,6 +74,44 @@ fn test_diff() { assert!(d2 < 4500); } +#[test] +fn test_diff_u32() { + const N: usize = 1000 / 4; + let mut v1 = [0u32; N]; + let mut v2 = [0u32; N]; + for v in v1.iter_mut() { + *v = getrandom::u32().unwrap(); + } + for v in v2.iter_mut() { + *v = getrandom::u32().unwrap(); + } + + // Between 3.5 and 4.5 bits per byte should differ. Probability of failure: + // ~ 2^(-94) = 2 * CDF[BinomialDistribution[8000, 0.5], 3500] + let d1 = num_diff_bits(&v1, &v2); + assert!(d1 > 3500); + assert!(d1 < 4500); +} + +#[test] +fn test_diff_u64() { + const N: usize = 1000 / 8; + let mut v1 = [0u64; N]; + let mut v2 = [0u64; N]; + for v in v1.iter_mut() { + *v = getrandom::u64().unwrap(); + } + for v in v2.iter_mut() { + *v = getrandom::u64().unwrap(); + } + + // Between 3.5 and 4.5 bits per byte should differ. Probability of failure: + // ~ 2^(-94) = 2 * CDF[BinomialDistribution[8000, 0.5], 3500] + let d1 = num_diff_bits(&v1, &v2); + assert!(d1 > 3500); + assert!(d1 < 4500); +} + #[test] fn test_small() { const N: usize = 64;