diff --git a/examples/random/src/main.rs b/examples/random/src/main.rs index f2c272a47..e6076b1a4 100644 --- a/examples/random/src/main.rs +++ b/examples/random/src/main.rs @@ -8,7 +8,7 @@ use riot_rs::debug::println; #[riot_rs::thread(autostart)] fn main() { use rand::Rng as _; - let mut rng = riot_rs::random::get_rng(); + let mut rng = riot_rs::random::fast_rng(); let value = rng.gen_range(1..=6); diff --git a/src/riot-rs-random/Cargo.toml b/src/riot-rs-random/Cargo.toml index 93d0debc8..5a432b013 100644 --- a/src/riot-rs-random/Cargo.toml +++ b/src/riot-rs-random/Cargo.toml @@ -7,15 +7,16 @@ repository.workspace = true [dependencies] rand_core = "0.6.4" -# Not making it optional during development b/c we don't have a small or fast one -rand_chacha = { version = "0.3.1", default-features = false } + embassy-sync.workspace = true +rand_pcg = "0.3.1" +rand_chacha = { version = "0.3.1", default-features = false, optional = true } + [lints] workspace = true [features] -## If set, the provided main RNG is also a cryptographically secure pseudo -## random number generator (CSPRNG) and thusly annotated by the -## rand_core::CryptoRngCore trait. -main-is-csprng = [] +## If set, the one global RNG is also a cryptographically secure pseudo +## random number generator (CSPRNG), and thus, a `CryptoRng` can be produced. +csprng = ["dep:rand_chacha"] diff --git a/src/riot-rs-random/src/lib.rs b/src/riot-rs-random/src/lib.rs index 1198edefb..1b4d85486 100644 --- a/src/riot-rs-random/src/lib.rs +++ b/src/riot-rs-random/src/lib.rs @@ -1,94 +1,128 @@ //! Provides a seeded random number generator depending on RIOT-rs's configuration. //! -//! The module provides a single function for use by applications, [`get_rng()`], produces an owned -//! RNG of type [`Rng`] to the application. +//! The module provides functions for use by applications, [`fast_rng()`] and [`crypto_rng()`], +//! which produce owned types that provide the [`rand_core::RngCore`] and +//! [`rand_core::CryptoRng`'] traits, respectively. //! //! The crate abstracts over multiple aspects of RNGs: //! * Where do we take a valid seed for the RNG from? //! * What's the type of RNG that we take along? -//! * Can some of those be shared? -//! (Currently, Rng is a zero-sized type shared across all users, and locks on each access. -//! That behavior may still change). +//! * Is RNG state shared across cores, threads, tasks or not at all? //! //! No matter the choices taken (eventually through the application's setup), all is hidden behind -//! a main [`Rng`] type, which depending on the feature `main-is-csprng` also implements -//! [`rand_core::CryptoRng`] +//! the [`FastRng`] and [`CryptoRng`] types. //! //! Before accessing the RNG, it needs to be initialized through the [`construct_rng()`'] function. //! This is taken care of by the `riot-rs-embassy` initialization functions. Applications can -//! ensure that this has happened by depending on the laze feature `rng`. +//! ensure that this has happened by depending on the laze feature `random`. //! //! --- //! //! Currently, this provides very little choice, and little fanciness: It (more or less -//! arbitrarily) uses the [rand_chacha::ChaCha20Rng] generator, and the zero-sized wrapper Rng -//! around it locks the global state on demand. Neither the algorithm nor the size of Rng is -//! guaranteed. +//! arbitrarily) uses the [rand_chacha::ChaCha20Rng] generator as a shared global RNG, and +//! [rand_pcg::Pcg32] is decided yet for the fast one. Neither the algorithm nor the size of +//! FastRng or CryptoRng is guaranteed. #![no_std] use rand_core::{RngCore, SeedableRng}; +/// A global RNG. // The Mutex can probably be simplified static RNG: embassy_sync::blocking_mutex::Mutex< embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex, core::cell::RefCell>, > = embassy_sync::blocking_mutex::Mutex::new(core::cell::RefCell::new(None)); -// This is one of the points where we can tune easily. If calls to `get_rng()` are rare, it may -// even make sense to move the HWRNG in here to get a global ZST. -// #[cfg(feature = "main-is-csprng")] +/// Type of the global RNG when needing the ability to produce cryptographially secure random +/// numbers. +/// +/// If calls to [`rng()`] are rare, it may even make sense to move the HWRNG in here to get a +/// ZST global. +#[cfg(feature = "csprng")] pub(crate) type SelectedRng = rand_chacha::ChaCha20Rng; -/// The OS provided random number generator. +/// Type of the global RNG when cryptographically secure random numbers are not needed. +#[cfg(not(feature = "csprng"))] +pub(crate) type SelectedRng = rand_pcg::Pcg32; + +/// Locks the global RNG for a single operation. /// -/// This may be small, fast and/or secure, but will sacrifice the earlier properties if the later -/// are needed by any system component. +/// ## Panics /// -/// Such an RNG can be requested by any component, and will always be seeded appropriately. +/// … if initialization did not happen. /// -/// There is no point in requesting a "small" RNG (if a fast RNG is built in, that will do just as -/// well). There may be a point in requesting a "fast" RNG, which it may make sense to introduce a -/// FastRng that particular components can request. (Until that is available, an application can -/// depend on a known fast RNG and seed it from the OS's RNG, but preferably there should be a -/// single configured implementation used across applications for ROM optimization, even if it -/// turns out to be beneficial to have a thread or task local RNG around in RAM for speed). -pub struct Rng { - // Make the type not Send - _private: core::marker::PhantomData<*const ()>, +/// ## Deadlocks +/// +/// … if the action attempts to lock RNG. +fn with_global(action: impl FnOnce(&mut SelectedRng) -> R) -> R { + RNG.lock(|i| { + action( + i.borrow_mut() + .as_mut() + .expect("Initialization should have populated RNG"), + ) + }) } -impl Rng { - fn with(&mut self, action: impl FnOnce(&mut SelectedRng) -> R) -> R { - RNG.lock(|i| { - action( - i.borrow_mut() - .as_mut() - .expect("Initialization should have populated RNG"), - ) - }) - } +/// The OS provided fast random number generator. +/// +/// This will generally be faster to produce random numbers than [`CryptoRng`]. +/// +/// Such an RNG can be requested by any component, and will always be seeded appropriately. +pub struct FastRng { + inner: rand_pcg::Pcg32, + // Make the type not Send to later allow using thread-locals + _private: core::marker::PhantomData<*const ()>, } // Re-implementing the trait rather than Deref'ing into inner: This avoids leaking implementation -// details to users who might otherwise come to depend on platform specifics of the Rng. -impl RngCore for Rng { +// details to users who might otherwise come to depend on platform specifics of the FastRng. +impl RngCore for FastRng { fn next_u32(&mut self) -> u32 { - self.with(|i| i.next_u32()) + self.inner.next_u32() } fn next_u64(&mut self) -> u64 { - self.with(|i| i.next_u64()) + self.inner.next_u64() } fn fill_bytes(&mut self, buf: &mut [u8]) { - self.with(|i| i.fill_bytes(buf)) + self.inner.fill_bytes(buf) } fn try_fill_bytes(&mut self, buf: &mut [u8]) -> Result<(), rand_core::Error> { - self.with(|i| i.try_fill_bytes(buf)) + self.inner.try_fill_bytes(buf) } } -#[cfg(feature = "main-is-csprng")] -mod main_is_csprng { - impl rand_core::CryptoRng for super::Rng {} +/// The OS provided cryptographically secure random number generator. +/// +/// Such an RNG can be requested by any component, and will always be seeded appropriately. +#[cfg(feature = "csprng")] +pub struct CryptoRng { + // Make the type not Send to later allow using thread-locals + pub(crate) _private: core::marker::PhantomData<*const ()>, +} + +#[cfg(feature = "csprng")] +mod csprng { + use super::*; + + // Re-implementing the trait rather than Deref'ing into inner: This avoids leaking implementation + // details to users who might otherwise come to depend on platform specifics of the CryptoRng. + impl RngCore for CryptoRng { + fn next_u32(&mut self) -> u32 { + with_global(|i| i.next_u32()) + } + fn next_u64(&mut self) -> u64 { + with_global(|i| i.next_u64()) + } + fn fill_bytes(&mut self, buf: &mut [u8]) { + with_global(|i| i.fill_bytes(buf)) + } + fn try_fill_bytes(&mut self, buf: &mut [u8]) -> Result<(), rand_core::Error> { + with_global(|i| i.try_fill_bytes(buf)) + } + } + + impl rand_core::CryptoRng for super::CryptoRng {} /// Asserts that SelectedRng is CryptoRng, justifying the implementation above. fn static_assert_is_cryptorng() -> impl rand_core::CryptoRng { @@ -97,7 +131,7 @@ mod main_is_csprng { } } -/// Populates the RNG from a seed value. +/// Populates the global RNG from a seed value. /// /// This is called by RIOT-rs's initialization functions. pub fn construct_rng(hwrng: impl RngCore) { @@ -108,14 +142,20 @@ pub fn construct_rng(hwrng: impl RngCore) { }); } -/// Obtains a suitably initialized random number generator. -/// -/// This may be used by threads or tasks. To avoid synchronizion overhead, in the future, -/// dependency injection for task and thread generation might be provided through the riot-rs -/// macros. +/// Obtains a suitably initialized fast random number generator. +#[inline] +pub fn fast_rng() -> FastRng { + FastRng { + inner: with_global(|i| rand_pcg::Pcg32::from_rng(i).expect("Global RNG is infallible")), + _private: Default::default(), + } +} + +/// Obtains a suitably initialized cryptographically secure random number generator. #[inline] -pub fn get_rng() -> Rng { - Rng { +#[cfg(feature = "csprng")] +pub fn crypto_rng() -> CryptoRng { + CryptoRng { _private: Default::default(), } } diff --git a/src/riot-rs/Cargo.toml b/src/riot-rs/Cargo.toml index 9ee608e67..3f5884f7b 100644 --- a/src/riot-rs/Cargo.toml +++ b/src/riot-rs/Cargo.toml @@ -45,8 +45,10 @@ threading = [ ## Enables support for timeouts in the internal executor---required to use ## `embassy_time::Timer`. time = ["riot-rs-embassy/time"] -## Enables the [`riot-rs::random`] module. +## Enables the `random` module. random = ["riot-rs-random"] +## Enables a cryptographically seucre random number generator in the `random` module. +csprng = ["riot-rs-random/csprng"] ## Enables seeding the random number generator from hardware. hwrng = ["riot-rs-embassy/hwrng"]