use core::sync::atomic::{
AtomicPtr,
Ordering,
};
use core::{
fmt,
ptr,
};
use crate::{
Error,
Result,
};
pub type EntropyCallback = unsafe extern "C" fn(dest: *mut u8, len: usize, context: *mut u8) -> i32;
#[derive(Debug, Clone, Copy)]
pub struct EntropyContext {
pub user_data: *mut u8,
pub size: usize,
}
impl EntropyContext {
pub const unsafe fn new(user_data: *mut u8, size: usize) -> Self {
Self { user_data, size }
}
#[must_use]
pub const fn empty() -> Self {
Self {
user_data: ptr::null_mut(),
size: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum EntropyQuality {
Hardware,
Os,
User,
Deterministic,
}
impl EntropyQuality {
#[must_use]
pub fn as_f64(self) -> f64 {
match self {
Self::Hardware => 1.0,
Self::Os => 0.95,
Self::User => 0.8,
Self::Deterministic => 0.0,
}
}
#[must_use]
pub fn is_secure(self) -> bool {
matches!(self, Self::Hardware | Self::Os | Self::User)
}
}
#[derive(Debug, Clone)]
pub struct CustomEntropyConfig {
pub min_quality: EntropyQuality,
pub max_bytes_per_call: usize,
pub validate_quality: bool,
pub timeout_ms: u32,
}
impl Default for CustomEntropyConfig {
fn default() -> Self {
Self {
min_quality: EntropyQuality::User,
max_bytes_per_call: 1024,
validate_quality: true,
timeout_ms: 1000,
}
}
}
#[derive(Debug)]
pub struct CustomEntropySource {
pub callback: EntropyCallback,
pub context: EntropyContext,
pub quality: EntropyQuality,
pub config: CustomEntropyConfig,
pub source_id: &'static str,
}
impl CustomEntropySource {
pub const unsafe fn new(
callback: EntropyCallback,
context: EntropyContext,
quality: EntropyQuality,
config: CustomEntropyConfig,
source_id: &'static str,
) -> Self {
Self {
callback,
context,
quality,
config,
source_id,
}
}
#[must_use]
pub fn callback(&self) -> EntropyCallback {
self.callback
}
#[must_use]
pub fn context(&self) -> EntropyContext {
self.context
}
#[must_use]
pub fn quality(&self) -> EntropyQuality {
self.quality
}
#[must_use]
pub fn config(&self) -> &CustomEntropyConfig {
&self.config
}
#[must_use]
pub fn source_id(&self) -> &'static str {
self.source_id
}
pub fn generate_entropy(&self, dest: &mut [u8]) -> Result<()> {
if dest.len() > self.config.max_bytes_per_call {
return Err(Error::EntropySourceUnavailable {
source: self.source_id,
context: Some("requested bytes exceed maximum per call"),
});
}
if !self.quality.is_secure() && self.config.validate_quality {
return Err(Error::EntropyValidationFailed {
reason: "entropy source quality too low",
quality: self.quality.as_f64(),
details: Some("deterministic sources not allowed in secure mode"),
});
}
let result =
unsafe { (self.callback)(dest.as_mut_ptr(), dest.len(), self.context.user_data) };
if result == 0 {
Ok(())
} else {
Err(Error::EntropySourceUnavailable {
source: self.source_id,
context: Some("custom entropy callback failed"),
})
}
}
}
pub struct ThreadEntropyRegistry {
source: AtomicPtr<CustomEntropySource>,
}
impl Default for ThreadEntropyRegistry {
fn default() -> Self {
Self::new()
}
}
impl ThreadEntropyRegistry {
#[must_use]
pub const fn new() -> Self {
Self {
source: AtomicPtr::new(ptr::null_mut()),
}
}
pub unsafe fn register(&self, source: *const CustomEntropySource) {
self.source.store(source.cast_mut(), Ordering::Release);
}
pub fn unregister(&self) {
self.source.store(ptr::null_mut(), Ordering::Release);
}
pub unsafe fn get_source(&self) -> Option<&CustomEntropySource> {
let ptr = self.source.load(Ordering::Acquire);
if ptr.is_null() {
None
} else {
unsafe { Some(&*ptr) }
}
}
pub fn generate_entropy(&self, dest: &mut [u8]) -> Result<()> {
unsafe {
if let Some(source) = self.get_source() {
source.generate_entropy(dest)
} else {
Err(Error::EntropySourceUnavailable {
source: "thread_local",
context: Some("no custom entropy source registered"),
})
}
}
}
}
thread_local! {
static THREAD_REGISTRY: ThreadEntropyRegistry = const { ThreadEntropyRegistry::new() };
}
pub unsafe fn register_custom_entropy_source(source: *const CustomEntropySource) {
THREAD_REGISTRY.with(|registry| unsafe { registry.register(source) });
}
pub fn unregister_custom_entropy_source() {
THREAD_REGISTRY.with(ThreadEntropyRegistry::unregister);
}
pub fn generate_custom_entropy(dest: &mut [u8]) -> Result<()> {
THREAD_REGISTRY.with(|registry| registry.generate_entropy(dest))
}
#[must_use]
pub fn has_custom_entropy_source() -> bool {
THREAD_REGISTRY.with(|registry| unsafe { registry.get_source().is_some() })
}
#[must_use]
pub fn get_entropy_source_info() -> Option<(&'static str, EntropyQuality)> {
THREAD_REGISTRY.with(|registry| unsafe {
registry
.get_source()
.map(|source| (source.source_id(), source.quality()))
})
}
impl fmt::Display for EntropyQuality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Hardware => write!(f, "Hardware"),
Self::Os => write!(f, "OS"),
Self::User => write!(f, "User"),
Self::Deterministic => write!(f, "Deterministic"),
}
}
}
impl fmt::Display for CustomEntropyConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"CustomEntropyConfig {{ min_quality: {}, max_bytes: {}, validate: {}, timeout: {}ms }}",
self.min_quality, self.max_bytes_per_call, self.validate_quality, self.timeout_ms
)
}
}
impl fmt::Display for CustomEntropySource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"CustomEntropySource {{ id: {}, quality: {}, config: {} }}",
self.source_id, self.quality, self.config
)
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use alloc::format;
use super::*;
#[allow(clippy::cast_possible_truncation)]
unsafe extern "C" fn test_entropy_callback(
dest: *mut u8,
len: usize,
_context: *mut u8,
) -> i32 {
if dest.is_null() {
return -1;
}
for i in 0..len {
unsafe {
*dest.add(i) = (i as u8).wrapping_add(42);
}
}
0
}
#[test]
fn test_entropy_context_creation() {
let context = EntropyContext::empty();
assert!(context.user_data.is_null());
assert_eq!(context.size, 0);
let data = [1u8, 2, 3, 4];
let context = unsafe { EntropyContext::new(data.as_ptr().cast_mut(), data.len()) };
assert!(!context.user_data.is_null());
assert_eq!(context.size, 4);
}
#[test]
fn test_entropy_quality() {
assert!(EntropyQuality::Hardware.is_secure());
assert!(EntropyQuality::Os.is_secure());
assert!(EntropyQuality::User.is_secure());
assert!(!EntropyQuality::Deterministic.is_secure());
assert!((EntropyQuality::Hardware.as_f64() - 1.0).abs() < f64::EPSILON);
assert!((EntropyQuality::Os.as_f64() - 0.95).abs() < f64::EPSILON);
assert!((EntropyQuality::User.as_f64() - 0.8).abs() < f64::EPSILON);
assert!((EntropyQuality::Deterministic.as_f64() - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_custom_entropy_config_default() {
let config = CustomEntropyConfig::default();
assert_eq!(config.min_quality, EntropyQuality::User);
assert_eq!(config.max_bytes_per_call, 1024);
assert!(config.validate_quality);
assert_eq!(config.timeout_ms, 1000);
}
#[test]
fn test_custom_entropy_source_creation() {
let context = EntropyContext::empty();
let config = CustomEntropyConfig::default();
let source = unsafe {
CustomEntropySource::new(
test_entropy_callback,
context,
EntropyQuality::User,
config,
"test_source",
)
};
assert_eq!(source.source_id(), "test_source");
assert_eq!(source.quality(), EntropyQuality::User);
}
#[test]
#[allow(clippy::cast_possible_truncation)]
fn test_custom_entropy_generation() {
let context = EntropyContext::empty();
let config = CustomEntropyConfig::default();
let source = unsafe {
CustomEntropySource::new(
test_entropy_callback,
context,
EntropyQuality::User,
config,
"test_source",
)
};
let mut buffer = [0u8; 16];
source.generate_entropy(&mut buffer).unwrap();
for (i, &byte) in buffer.iter().enumerate() {
let expected = (i as u8).wrapping_add(42);
assert_eq!(byte, expected);
}
}
#[test]
fn test_custom_entropy_max_bytes_validation() {
let context = EntropyContext::empty();
let config = CustomEntropyConfig {
max_bytes_per_call: 8,
..Default::default()
};
let source = unsafe {
CustomEntropySource::new(
test_entropy_callback,
context,
EntropyQuality::User,
config,
"test_source",
)
};
let mut buffer = [0u8; 16]; let result = source.generate_entropy(&mut buffer);
assert!(result.is_err());
}
#[test]
fn test_custom_entropy_quality_validation() {
let context = EntropyContext::empty();
let config = CustomEntropyConfig {
validate_quality: true,
..Default::default()
};
let source = unsafe {
CustomEntropySource::new(
test_entropy_callback,
context,
EntropyQuality::Deterministic, config,
"test_source",
)
};
let mut buffer = [0u8; 8];
let result = source.generate_entropy(&mut buffer);
assert!(result.is_err());
}
#[test]
fn test_thread_entropy_registry() {
let _registry = ThreadEntropyRegistry::new();
assert!(!has_custom_entropy_source());
assert!(get_entropy_source_info().is_none());
let context = EntropyContext::empty();
let config = CustomEntropyConfig::default();
let source = CustomEntropySource {
callback: test_entropy_callback,
context,
quality: EntropyQuality::User,
config,
source_id: "test_registry",
};
unsafe {
register_custom_entropy_source(&raw const source);
}
assert!(has_custom_entropy_source());
let info = get_entropy_source_info().unwrap();
assert_eq!(info.0, "test_registry");
assert_eq!(info.1, EntropyQuality::User);
let mut buffer = [0u8; 8];
generate_custom_entropy(&mut buffer).unwrap();
unregister_custom_entropy_source();
assert!(!has_custom_entropy_source());
assert!(get_entropy_source_info().is_none());
}
#[test]
#[cfg(feature = "alloc")]
fn test_entropy_source_display() {
let context = EntropyContext::empty();
let config = CustomEntropyConfig::default();
let source = unsafe {
CustomEntropySource::new(
test_entropy_callback,
context,
EntropyQuality::Hardware,
config,
"display_test",
)
};
let display = format!("{source}");
assert!(display.contains("display_test"));
assert!(display.contains("Hardware"));
}
}