use alloc::rc::Rc;
use core::cell::RefCell;
use thiserror::Error;
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum EntropyError {
#[error("no entropy source available: {reason}")]
Unavailable {
reason: &'static str,
},
#[error("entropy source failed: {reason}")]
Failed {
reason: &'static str,
code: Option<i32>,
},
}
impl EntropyError {
pub const fn unavailable(reason: &'static str) -> Self {
Self::Unavailable { reason }
}
pub const fn failed(reason: &'static str) -> Self {
Self::Failed { reason, code: None }
}
pub const fn failed_with_code(reason: &'static str, code: i32) -> Self {
Self::Failed {
reason,
code: Some(code),
}
}
}
pub trait EntropySource {
fn fill_bytes(&mut self, output: &mut [u8]) -> Result<(), EntropyError>;
fn next_u64(&mut self) -> Result<u64, EntropyError> {
let mut bytes = [0u8; 8];
self.fill_bytes(&mut bytes)?;
Ok(u64::from_le_bytes(bytes))
}
}
pub struct SharedEntropy<E> {
inner: Rc<RefCell<E>>,
}
impl<E> SharedEntropy<E> {
pub fn new(source: E) -> Self {
Self {
inner: Rc::new(RefCell::new(source)),
}
}
}
impl<E> Clone for SharedEntropy<E> {
fn clone(&self) -> Self {
Self {
inner: Rc::clone(&self.inner),
}
}
}
impl<E: EntropySource> EntropySource for SharedEntropy<E> {
fn fill_bytes(&mut self, output: &mut [u8]) -> Result<(), EntropyError> {
self.inner.borrow_mut().fill_bytes(output)
}
fn next_u64(&mut self) -> Result<u64, EntropyError> {
self.inner.borrow_mut().next_u64()
}
}
impl<E: EntropySource + ?Sized> EntropySource for &mut E {
fn fill_bytes(&mut self, output: &mut [u8]) -> Result<(), EntropyError> {
(**self).fill_bytes(output)
}
fn next_u64(&mut self) -> Result<u64, EntropyError> {
(**self).next_u64()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::boxed::Box;
struct Counter(u8);
impl EntropySource for Counter {
fn fill_bytes(&mut self, output: &mut [u8]) -> Result<(), EntropyError> {
for byte in output.iter_mut() {
*byte = self.0;
self.0 = self.0.wrapping_add(1);
}
Ok(())
}
}
struct Broken;
impl EntropySource for Broken {
fn fill_bytes(&mut self, output: &mut [u8]) -> Result<(), EntropyError> {
if let Some(first) = output.first_mut() {
*first = 0xff;
}
Err(EntropyError::failed_with_code("rng offline", 5))
}
}
#[test]
fn fill_bytes_fills_the_whole_slice() {
let mut source = Counter(1);
let mut buffer = [0u8; 4];
source.fill_bytes(&mut buffer).expect("fill");
assert_eq!(buffer, [1, 2, 3, 4]);
}
#[test]
fn next_u64_reads_eight_little_endian_bytes() {
let mut source = Counter(1);
let value = source.next_u64().expect("draw");
assert_eq!(value, u64::from_le_bytes([1, 2, 3, 4, 5, 6, 7, 8]));
}
#[test]
fn shared_handles_advance_one_underlying_stream() {
let mut first = SharedEntropy::new(Counter(1));
let mut second = first.clone();
let mut a = [0; 2];
let mut b = [0; 2];
first.fill_bytes(&mut a).expect("first draw");
second.fill_bytes(&mut b).expect("second draw");
assert_eq!(a, [1, 2]);
assert_eq!(b, [3, 4]);
}
#[test]
fn failures_propagate_through_next_u64() {
let mut source = Broken;
assert_eq!(
source.next_u64(),
Err(EntropyError::Failed {
reason: "rng offline",
code: Some(5)
})
);
}
fn draw<E: EntropySource>(mut source: E, buffer: &mut [u8]) -> Result<u64, EntropyError> {
source.fill_bytes(buffer)?;
source.next_u64()
}
#[test]
fn mutable_reference_forwards_to_inner_source() {
let mut source = Counter(1);
let mut buffer = [0u8; 2];
let drawn = draw(&mut source, &mut buffer).expect("draw");
assert_eq!(buffer, [1, 2]);
assert_eq!(drawn, u64::from_le_bytes([3, 4, 5, 6, 7, 8, 9, 10]));
assert_eq!(source.0, 11);
}
#[test]
fn trait_is_object_safe() {
let mut source: Box<dyn EntropySource> = Box::new(Counter(9));
let mut buffer = [0u8; 2];
source.fill_bytes(&mut buffer).expect("fill");
assert_eq!(buffer, [9, 10]);
}
}