Skip to content

Commit

Permalink
random: Have separate "fast" and "secure" RNGs
Browse files Browse the repository at this point in the history
This avoids surprises for users where the "default" one suddenly becomes
slow because some module needs a CSPRNG.
  • Loading branch information
chrysn authored and kaspar030 committed Apr 12, 2024
1 parent 4eb2a70 commit c905b8c
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 62 deletions.
2 changes: 1 addition & 1 deletion examples/random/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
13 changes: 7 additions & 6 deletions src/riot-rs-random/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
148 changes: 94 additions & 54 deletions src/riot-rs-random/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<RefCell> can probably be simplified
static RNG: embassy_sync::blocking_mutex::Mutex<
embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex,
core::cell::RefCell<Option<SelectedRng>>,
> = 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<R>(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<R>(&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 {
Expand All @@ -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) {
Expand All @@ -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(),
}
}
4 changes: 3 additions & 1 deletion src/riot-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down

0 comments on commit c905b8c

Please sign in to comment.