diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e3c9bbe6..2b6023e1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,10 +33,10 @@ jobs: - name: Generate Docs env: RUSTDOCFLAGS: --cfg docsrs - run: cargo deadlinks -- --features=custom,std + run: cargo deadlinks -- --features=custom,std,zerocopy - run: | cargo generate-lockfile -Z minimal-versions - cargo test --features=custom,std + cargo test --features=custom,std,zerocopy # TODO: add aarch64-based macOS runner when it's supported by Github Actions main-tests: @@ -80,7 +80,7 @@ jobs: - name: Install multilib run: sudo apt-get update && sudo apt-get install gcc-multilib - uses: Swatinem/rust-cache@v2 - - run: cargo test --target=${{ matrix.target }} --features=std + - run: cargo test --target=${{ matrix.target }} --features=std,zerocopy ios-tests: name: iOS Simulator Test @@ -128,7 +128,7 @@ jobs: with: toolchain: ${{ matrix.toolchain }} - uses: Swatinem/rust-cache@v2 - - run: cargo test --features=std + - run: cargo test --features=std,zerocopy cross-tests: name: Cross Test @@ -152,7 +152,7 @@ jobs: wget -O - $URL | tar -xz -C ~/.cargo/bin cross --version - name: Test - run: cross test --no-fail-fast --target=${{ matrix.target }} --features=std + run: cross test --no-fail-fast --target=${{ matrix.target }} --features=std,zerocopy macos-link: name: macOS ARM64 Build/Link @@ -164,9 +164,9 @@ jobs: targets: aarch64-apple-darwin, aarch64-apple-ios components: rust-src - uses: Swatinem/rust-cache@v2 - - run: cargo test --no-run --target=aarch64-apple-darwin --features=std - - run: cargo test --no-run --target=aarch64-apple-ios --features=std - - run: cargo test --no-run --target=aarch64-apple-watchos-sim -Zbuild-std --features=std + - run: cargo test --no-run --target=aarch64-apple-darwin --features=std,zerocopy + - run: cargo test --no-run --target=aarch64-apple-ios --features=std,zerocopy + - run: cargo test --no-run --target=aarch64-apple-watchos-sim -Zbuild-std --features=std,zerocopy cross-link: name: Cross Build/Link @@ -188,7 +188,7 @@ jobs: wget -O - $URL | tar -xz -C ~/.cargo/bin cross --version - name: Build Tests - run: cross test --no-run --target=${{ matrix.target }} --features=std + run: cross test --no-run --target=${{ matrix.target }} --features=std,zerocopy web-tests: name: Web Test @@ -284,7 +284,7 @@ jobs: targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 - name: Build - run: cargo build --target=${{ matrix.target }} --features=std + run: cargo build --target=${{ matrix.target }} --features=std,zerocopy build-tier3: name: Tier 3 Build @@ -348,6 +348,6 @@ jobs: components: rustfmt, clippy - uses: Swatinem/rust-cache@v2 - name: clippy - run: cargo clippy --all --features=custom,std + run: cargo clippy --all --features=custom,std,zerocopy - name: fmt run: cargo fmt --all -- --check diff --git a/Cargo.toml b/Cargo.toml index ea57331d..4e7cfb6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ exclude = [".*"] [dependencies] cfg-if = "1" +# WARNING: Enabling this dependency bumps MSRV to 1.60 +zerocopy = { version = "0.7", optional = true, default-features = false } # When built as part of libstd compiler_builtins = { version = "0.1", optional = true } @@ -49,7 +51,7 @@ rustc-dep-of-std = [ test-in-browser = [] [package.metadata.docs.rs] -features = ["std", "custom"] +features = ["std", "custom", "zerocopy"] rustdoc-args = ["--cfg", "docsrs"] # workaround for https://github.com/cross-rs/cross/issues/1345 diff --git a/src/lib.rs b/src/lib.rs index e80ec6f5..ec393d0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -195,6 +195,9 @@ #[macro_use] extern crate cfg_if; +#[cfg(feature = "zerocopy")] +pub use zerocopy; + use crate::util::{slice_as_uninit_mut, slice_assume_init_mut}; use core::mem::MaybeUninit; @@ -350,3 +353,33 @@ pub fn getrandom_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error // since it returned `Ok`. Ok(unsafe { slice_assume_init_mut(dest) }) } + +/// Generate a random value of type `T` implementing the [`zerocopy::FromBytes`] trait. +/// +/// # Examples +/// ``` +/// # fn main() -> Result<(), getrandom::Error> { +/// let key: [u8; 16] = getrandom::value()?; +/// let keys: [[u8; 16]; 64] = getrandom::value()?; +/// let random_u32: u32 = getrandom::value()?; +/// let random_u64s: [u64; 100] = getrandom::value()?; +/// # Ok(()) } +/// ``` +#[cfg(feature = "zerocopy")] +#[inline] +pub fn value() -> Result { + let mut value = MaybeUninit::::uninit(); + // SAFETY: it's safe to cast `&mut MaybeUninit` to `&mut [MaybeUninit]` + // with slice length equal to `size_of::()`. The compiler will ensure that + // `T` isn't too large. + unsafe { + let ptr: *mut MaybeUninit = value.as_mut_ptr().cast(); + let size = core::mem::size_of::(); + let uninit_bytes = core::slice::from_raw_parts_mut(ptr, size); + getrandom_uninit(uninit_bytes)?; + }; + // SAFETY: when `getrandom_uninit` returns `Ok` all bytes in `uninit_bytes` + // (and thus in `value`) are properly initialized. Any bit-sequence is valid + // for `T: FromBytes`, so we can safely execute `assume_init` on `value`. + Ok(unsafe { value.assume_init() }) +}