diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9b127f472..79748acc6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -269,7 +269,7 @@ jobs: targets: riscv32i-unknown-none-elf - uses: Swatinem/rust-cache@v2 - env: - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="custom" + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="unsupported" run: cargo build --target riscv32i-unknown-none-elf unsupported: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9c114d7b2..d869dd282 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -78,8 +78,8 @@ jobs: RUSTDOCFLAGS: -Dwarnings --cfg getrandom_backend="rdrand" run: cargo test --features=std,sys_rng - extern_item_impls: - name: Extern Item Implementations + custom_impl: + name: Custom and External Implementations runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 @@ -88,9 +88,13 @@ jobs: toolchain: nightly - uses: Swatinem/rust-cache@v2 - env: - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="extern_item_impls" - RUSTDOCFLAGS: -Dwarnings --cfg getrandom_backend="extern_item_impls" - run: cargo test --features=std --test mod extern_item_impls + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="custom" + working_directory: custom_impl_test + run: cargo test + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="extern_impl" + working_directory: custom_impl_test + run: cargo test ios: name: iOS Simulator diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index 8babfaaba..feea1b46d 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -114,7 +114,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Generate Docs env: - RUSTDOCFLAGS: "-Dwarnings --cfg docsrs" + RUSTDOCFLAGS: -Dwarnings --cfg docsrs --cfg getrandom_backend="extern_impl" run: cargo doc --no-deps --features std,sys_rng typos: diff --git a/.gitignore b/.gitignore index 51778e4a6..0aa670e23 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ **/*.rs.bk nopanic_check/Cargo.lock nopanic_check/target/ +custom_impl_test/Cargo.lock +custom_impl_test/target/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c36092be..b8e7d1ff7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `RawOsError` type alias [#739] - `SysRng` behind new feature `sys_rng` [#751] - WASIp3 support [#779] +- `extern_impl` opt-in backend [#786] [#794] - Motor OS support [#797] ### Changed @@ -19,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#749]: https://github.com/rust-random/getrandom/pull/749 [#751]: https://github.com/rust-random/getrandom/pull/751 [#779]: https://github.com/rust-random/getrandom/pull/779 +[#786]: https://github.com/rust-random/getrandom/pull/786 +[#794]: https://github.com/rust-random/getrandom/pull/794 ## [0.3.4] - 2025-10-14 diff --git a/Cargo.toml b/Cargo.toml index 16bb21410..21ad16d63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ exclude = [".*"] [package.metadata.docs.rs] features = ["std", "sys_rng"] +rustdoc-args = ["--cfg getrandom_backend=\"extern_impl\""] [features] # Implement From for std::io::Error and @@ -94,7 +95,7 @@ wasm-bindgen-test = "0.3" [lints.rust.unexpected_cfgs] level = "warn" check-cfg = [ - 'cfg(getrandom_backend, values("custom", "efi_rng", "rdrand", "rndr", "linux_getrandom", "linux_raw", "windows_legacy", "unsupported", "extern_item_impls"))', + 'cfg(getrandom_backend, values("custom", "efi_rng", "rdrand", "rndr", "linux_getrandom", "linux_raw", "windows_legacy", "unsupported", "extern_impl"))', 'cfg(getrandom_msan)', 'cfg(getrandom_test_linux_fallback)', 'cfg(getrandom_test_linux_without_fallback)', diff --git a/README.md b/README.md index db2726feb..bf19f13b0 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ of randomness based on their specific needs: | `windows_legacy` | Windows | `*-windows-*` | [`RtlGenRandom`] | `custom` | All targets | `*` | User-provided custom implementation (see [custom backend]) | `unsupported` | All targets | `*` | Always returns `Err(Error::UNSUPPORTED)` (see [unsupported backend]) -| `extern_item_impls` | All targets | `*` | User or library provided custom implementation (see [externally implemented interface]) +| `extern_impl` | All targets | `*` | Externally-provided custom implementation (see [externally implemented interface]) Opt-in backends can be enabled using the `getrandom_backend` configuration flag. The flag can be set either by specifying the `rustflags` field in [`.cargo/config.toml`]: @@ -205,17 +205,18 @@ unsafe extern "Rust" fn __getrandom_v03_custom( ### Externally Implemented Interface -Using the nightly-only feature [`extern_item_impls`](https://github.com/rust-lang/rust/issues/125418) -it is possible to provide a custom backend for `getrandom`, even to override -an existing first-party implementation. First, enable the `extern_item_impls` -opt-in backend to allow usage of this nightly feature. Then, you may provide -implementations for `fill_uninit`, `u32`, and/or `u64` with an attribute macro -from the `implementation` module. +Using the nightly-only feature [`extern_item_impls`] it is possible to provide +a custom backend for `getrandom`, even to override an existing first-party implementation. +First, enable the `extern_impl` opt-in backend to allow usage of this nightly feature. +Then, you may provide implementations for `fill_uninit`, `u32`, and/or `u64` +with an attribute macro from the `implementation` module. + +[`extern_item_impls`]: https://github.com/rust-lang/rust/issues/125418 ```rust use core::mem::MaybeUninit; -#[cfg(getrandom_backend = "extern_item_impls")] +#[cfg(getrandom_backend = "extern_impl")] #[getrandom::implementation::fill_uninit] fn my_fill_uninit_implementation( dest: &mut [MaybeUninit] diff --git a/custom_impl_test/Cargo.toml b/custom_impl_test/Cargo.toml new file mode 100644 index 000000000..8e75f0a16 --- /dev/null +++ b/custom_impl_test/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "custom_impl_test" +description = "Helper crate for testing custom implementations" +version = "0.1.0" +edition = "2024" +publish = false + +[workspace] + +[dependencies] +getrandom = { path = ".." } + +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = [ + 'cfg(getrandom_backend, values("custom", "extern_impl"))', +] diff --git a/custom_impl_test/src/lib.rs b/custom_impl_test/src/lib.rs new file mode 100644 index 000000000..8159f49ff --- /dev/null +++ b/custom_impl_test/src/lib.rs @@ -0,0 +1,101 @@ +use core::mem::MaybeUninit; +use getrandom::Error; + +/// Chosen by fair dice roll. +const SEED: u64 = 0x9095_810F_1B2B_E175; + +struct Xoshiro128PlusPlus { + s: [u32; 4], +} + +impl Xoshiro128PlusPlus { + fn new(mut seed: u64) -> Self { + const PHI: u64 = 0x9e3779b97f4a7c15; + let mut s = [0u32; 4]; + for val in s.iter_mut() { + seed = seed.wrapping_add(PHI); + let mut z = seed; + z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9); + z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb); + z = z ^ (z >> 31); + *val = z as u32; + } + Self { s } + } + + fn next_u32(&mut self) -> u32 { + let res = self.s[0] + .wrapping_add(self.s[3]) + .rotate_left(7) + .wrapping_add(self.s[0]); + + let t = self.s[1] << 9; + + self.s[2] ^= self.s[0]; + self.s[3] ^= self.s[1]; + self.s[1] ^= self.s[2]; + self.s[0] ^= self.s[3]; + + self.s[2] ^= t; + + self.s[3] = self.s[3].rotate_left(11); + + res + } +} + +pub fn custom_impl(dst: &mut [MaybeUninit]) -> Result<(), Error> { + let mut rng = Xoshiro128PlusPlus::new(SEED); + + let mut chunks = dst.chunks_exact_mut(4); + for chunk in &mut chunks { + let val = rng.next_u32(); + let dst_ptr = chunk.as_mut_ptr().cast::(); + unsafe { core::ptr::write_unaligned(dst_ptr, val) }; + } + let rem = chunks.into_remainder(); + if !rem.is_empty() { + let val = rng.next_u32(); + let src_ptr = &val as *const u32 as *const MaybeUninit; + assert!(rem.len() <= 4); + unsafe { core::ptr::copy(src_ptr, rem.as_mut_ptr(), rem.len()) }; + } + Ok(()) +} + +#[cfg(getrandom_backend = "custom")] +#[unsafe(no_mangle)] +unsafe extern "Rust" fn __getrandom_v03_custom(dst_ptr: *mut u8, len: usize) -> Result<(), Error> { + let dst = unsafe { core::slice::from_raw_parts_mut(dst_ptr.cast(), len) }; + custom_impl(dst) +} + +#[cfg(getrandom_backend = "extern_impl")] +#[getrandom::implementation::fill_uninit] +fn my_fill_uninit_implementation(dst: &mut [MaybeUninit]) -> Result<(), Error> { + custom_impl(dst) +} + +#[test] +fn test_custom_fill() { + let mut buf1 = [0u8; 256]; + getrandom::fill(&mut buf1).unwrap(); + + let mut buf2 = [0u8; 256]; + custom_impl(unsafe { core::slice::from_raw_parts_mut(buf2.as_mut_ptr().cast(), buf2.len()) }) + .unwrap(); + + assert_eq!(buf1, buf2); +} + +#[test] +fn test_custom_u32() { + let res = getrandom::u32().unwrap(); + assert_eq!(res, 0xEAD5_840A); +} + +#[test] +fn test_custom_u64() { + let res = getrandom::u64().unwrap(); + assert_eq!(res, 0xA856_FCC4_EAD5_840A); +} diff --git a/src/backends.rs b/src/backends.rs index 1bbec0819..95547d9d3 100644 --- a/src/backends.rs +++ b/src/backends.rs @@ -32,9 +32,9 @@ cfg_if! { } else if #[cfg(getrandom_backend = "unsupported")] { mod unsupported; pub use unsupported::*; - } else if #[cfg(getrandom_backend = "extern_item_impls")] { - pub(crate) mod extern_item_impls; - pub use extern_item_impls::*; + } else if #[cfg(getrandom_backend = "extern_impl")] { + pub(crate) mod extern_impl; + pub use extern_impl::*; } else if #[cfg(all(target_os = "linux", target_env = ""))] { mod linux_raw; pub use linux_raw::*; diff --git a/src/backends/extern_item_impls.rs b/src/backends/extern_impl.rs similarity index 100% rename from src/backends/extern_item_impls.rs rename to src/backends/extern_impl.rs diff --git a/src/lib.rs b/src/lib.rs index f679f603a..ecec90026 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ #![doc = include_str!("../README.md")] #![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(getrandom_backend = "efi_rng", feature(uefi_std))] -#![cfg_attr(getrandom_backend = "extern_item_impls", feature(extern_item_impls))] +#![cfg_attr(getrandom_backend = "extern_impl", feature(extern_item_impls))] #[macro_use] extern crate cfg_if; @@ -35,18 +35,19 @@ pub use sys_rng::SysRng; pub use crate::error::{Error, RawOsError}; -/// Provides Externally Implementable Interfaces for the core functionality of this crate. +/// Attribute macros for overwriting the core functionality of this crate. +/// /// This allows `getrandom` to provide a default implementation and a common interface /// for all crates to use, while giving users a safe way to override that default where required. /// -/// Must be enabled via the `extern_item_impls` opt-in backend, as this functionality +/// Must be enabled via the `extern_impl` opt-in backend, as this functionality /// is currently limited to nightly. /// /// # Examples /// /// ```rust /// # use core::mem::MaybeUninit; -/// # #[cfg(getrandom_backend = "extern_item_impls")] +/// # #[cfg(getrandom_backend = "extern_impl")] /// #[getrandom::implementation::fill_uninit] /// fn my_fill_uninit_implementation( /// dest: &mut [MaybeUninit] @@ -56,9 +57,9 @@ pub use crate::error::{Error, RawOsError}; /// # Err(Error::UNSUPPORTED) /// } /// ``` -#[cfg(getrandom_backend = "extern_item_impls")] +#[cfg(getrandom_backend = "extern_impl")] pub mod implementation { - pub use crate::backends::extern_item_impls::{fill_uninit, u32, u64}; + pub use crate::backends::extern_impl::{fill_uninit, u32, u64}; } /// Fill `dest` with random bytes from the system's preferred random number source. diff --git a/tests/mod.rs b/tests/mod.rs index 3c6bf9904..48ca59d24 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -207,120 +207,3 @@ fn test_multithreading() { tx.send(()).unwrap(); } } - -#[cfg(getrandom_backend = "custom")] -mod custom { - use getrandom::Error; - - struct Xoshiro128PlusPlus { - s: [u32; 4], - } - - impl Xoshiro128PlusPlus { - fn new(mut seed: u64) -> Self { - const PHI: u64 = 0x9e3779b97f4a7c15; - let mut s = [0u32; 4]; - for val in s.iter_mut() { - seed = seed.wrapping_add(PHI); - let mut z = seed; - z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9); - z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb); - z = z ^ (z >> 31); - *val = z as u32; - } - Self { s } - } - - fn next_u32(&mut self) -> u32 { - let res = self.s[0] - .wrapping_add(self.s[3]) - .rotate_left(7) - .wrapping_add(self.s[0]); - - let t = self.s[1] << 9; - - self.s[2] ^= self.s[0]; - self.s[3] ^= self.s[1]; - self.s[1] ^= self.s[2]; - self.s[0] ^= self.s[3]; - - self.s[2] ^= t; - - self.s[3] = self.s[3].rotate_left(11); - - res - } - } - - // This implementation uses current timestamp as a PRNG seed. - // - // WARNING: this custom implementation is for testing purposes ONLY! - - #[unsafe(no_mangle)] - unsafe extern "Rust" fn __getrandom_v03_custom(dest: *mut u8, len: usize) -> Result<(), Error> { - use std::time::{SystemTime, UNIX_EPOCH}; - - assert_ne!(len, 0); - - if len == 142 { - return Err(Error::new_custom(142)); - } - - let dest_u32 = dest.cast::(); - let ts = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - let mut rng = Xoshiro128PlusPlus::new(ts.as_nanos() as u64); - for i in 0..len / 4 { - let val = rng.next_u32(); - unsafe { core::ptr::write_unaligned(dest_u32.add(i), val) }; - } - if len % 4 != 0 { - let start = 4 * (len / 4); - for i in start..len { - let val = rng.next_u32(); - unsafe { core::ptr::write_unaligned(dest.add(i), val as u8) }; - } - } - Ok(()) - } - - // Test that enabling the custom feature indeed uses the custom implementation - #[test] - fn test_custom() { - let mut buf = [0u8; 142]; - let res = getrandom::fill(&mut buf); - assert!(res.is_err()); - } -} - -#[cfg(getrandom_backend = "extern_item_impls")] -mod extern_item_impls { - use core::mem::MaybeUninit; - use getrandom::Error; - - // This implementation for fill_uninit will always fail. - // - // WARNING: this custom implementation is for testing purposes ONLY! - - #[getrandom::implementation::fill_uninit] - fn my_fill_uninit_implementation(_dest: &mut [MaybeUninit]) -> Result<(), Error> { - Err(Error::new_custom(4)) - } - - // This implementation returns a fixed value to demonstrate overriding defaults. - // - // WARNING: this custom implementation is for testing purposes ONLY! - - #[getrandom::implementation::u32] - fn my_u32_implementation() -> Result { - // Chosen by fair dice roll - Ok(4) - } - - // Test that enabling the custom feature indeed uses the custom implementation - #[test] - fn test_extern_item_impls() { - let mut buf = [0u8; 123]; - assert_eq!(getrandom::fill(&mut buf), Err(Error::new_custom(4))); - assert_eq!(getrandom::u32(), Ok(4)); - } -}