1use core::sync::atomic::{
9 AtomicPtr,
10 Ordering,
11};
12use core::{
13 fmt,
14 ptr,
15};
16
17use crate::{
18 Error,
19 Result,
20};
21
22pub type EntropyCallback = unsafe extern "C" fn(dest: *mut u8, len: usize, context: *mut u8) -> i32;
42
43#[derive(Debug, Clone, Copy)]
48pub struct EntropyContext {
49 pub user_data: *mut u8,
51 pub size: usize,
53}
54
55impl EntropyContext {
56 pub const unsafe fn new(user_data: *mut u8, size: usize) -> Self {
67 Self { user_data, size }
68 }
69
70 #[must_use]
72 pub const fn empty() -> Self {
73 Self {
74 user_data: ptr::null_mut(),
75 size: 0,
76 }
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
82pub enum EntropyQuality {
83 Hardware,
85 Os,
87 User,
89 Deterministic,
91}
92
93impl EntropyQuality {
94 #[must_use]
96 pub fn as_f64(self) -> f64 {
97 match self {
98 Self::Hardware => 1.0,
99 Self::Os => 0.95,
100 Self::User => 0.8,
101 Self::Deterministic => 0.0,
102 }
103 }
104
105 #[must_use]
107 pub fn is_secure(self) -> bool {
108 matches!(self, Self::Hardware | Self::Os | Self::User)
109 }
110}
111
112#[derive(Debug, Clone)]
114pub struct CustomEntropyConfig {
115 pub min_quality: EntropyQuality,
117 pub max_bytes_per_call: usize,
119 pub validate_quality: bool,
121 pub timeout_ms: u32,
123}
124
125impl Default for CustomEntropyConfig {
126 fn default() -> Self {
127 Self {
128 min_quality: EntropyQuality::User,
129 max_bytes_per_call: 1024,
130 validate_quality: true,
131 timeout_ms: 1000,
132 }
133 }
134}
135
136#[derive(Debug)]
141pub struct CustomEntropySource {
142 pub callback: EntropyCallback,
144 pub context: EntropyContext,
146 pub quality: EntropyQuality,
148 pub config: CustomEntropyConfig,
150 pub source_id: &'static str,
152}
153
154impl CustomEntropySource {
155 pub const unsafe fn new(
171 callback: EntropyCallback,
172 context: EntropyContext,
173 quality: EntropyQuality,
174 config: CustomEntropyConfig,
175 source_id: &'static str,
176 ) -> Self {
177 Self {
178 callback,
179 context,
180 quality,
181 config,
182 source_id,
183 }
184 }
185
186 #[must_use]
188 pub fn callback(&self) -> EntropyCallback {
189 self.callback
190 }
191
192 #[must_use]
194 pub fn context(&self) -> EntropyContext {
195 self.context
196 }
197
198 #[must_use]
200 pub fn quality(&self) -> EntropyQuality {
201 self.quality
202 }
203
204 #[must_use]
206 pub fn config(&self) -> &CustomEntropyConfig {
207 &self.config
208 }
209
210 #[must_use]
212 pub fn source_id(&self) -> &'static str {
213 self.source_id
214 }
215
216 pub fn generate_entropy(&self, dest: &mut [u8]) -> Result<()> {
227 if dest.len() > self.config.max_bytes_per_call {
228 return Err(Error::EntropySourceUnavailable {
229 source: self.source_id,
230 context: Some("requested bytes exceed maximum per call"),
231 });
232 }
233
234 if !self.quality.is_secure() && self.config.validate_quality {
235 return Err(Error::EntropyValidationFailed {
236 reason: "entropy source quality too low",
237 quality: self.quality.as_f64(),
238 details: Some("deterministic sources not allowed in secure mode"),
239 });
240 }
241
242 let result =
244 unsafe { (self.callback)(dest.as_mut_ptr(), dest.len(), self.context.user_data) };
245
246 if result == 0 {
247 Ok(())
248 } else {
249 Err(Error::EntropySourceUnavailable {
250 source: self.source_id,
251 context: Some("custom entropy callback failed"),
252 })
253 }
254 }
255}
256
257pub struct ThreadEntropyRegistry {
262 source: AtomicPtr<CustomEntropySource>,
264}
265
266impl Default for ThreadEntropyRegistry {
267 fn default() -> Self {
268 Self::new()
269 }
270}
271
272impl ThreadEntropyRegistry {
273 #[must_use]
275 pub const fn new() -> Self {
276 Self {
277 source: AtomicPtr::new(ptr::null_mut()),
278 }
279 }
280
281 pub unsafe fn register(&self, source: *const CustomEntropySource) {
293 self.source.store(source.cast_mut(), Ordering::Release);
294 }
295
296 pub fn unregister(&self) {
298 self.source.store(ptr::null_mut(), Ordering::Release);
299 }
300
301 pub unsafe fn get_source(&self) -> Option<&CustomEntropySource> {
313 let ptr = self.source.load(Ordering::Acquire);
314 if ptr.is_null() {
315 None
316 } else {
317 unsafe { Some(&*ptr) }
318 }
319 }
320
321 pub fn generate_entropy(&self, dest: &mut [u8]) -> Result<()> {
331 unsafe {
332 if let Some(source) = self.get_source() {
333 source.generate_entropy(dest)
334 } else {
335 Err(Error::EntropySourceUnavailable {
336 source: "thread_local",
337 context: Some("no custom entropy source registered"),
338 })
339 }
340 }
341 }
342}
343
344thread_local! {
345 static THREAD_REGISTRY: ThreadEntropyRegistry = const { ThreadEntropyRegistry::new() };
346}
347
348pub unsafe fn register_custom_entropy_source(source: *const CustomEntropySource) {
360 THREAD_REGISTRY.with(|registry| unsafe { registry.register(source) });
361}
362
363pub fn unregister_custom_entropy_source() {
365 THREAD_REGISTRY.with(ThreadEntropyRegistry::unregister);
366}
367
368pub fn generate_custom_entropy(dest: &mut [u8]) -> Result<()> {
378 THREAD_REGISTRY.with(|registry| registry.generate_entropy(dest))
379}
380
381#[must_use]
383pub fn has_custom_entropy_source() -> bool {
384 THREAD_REGISTRY.with(|registry| unsafe { registry.get_source().is_some() })
385}
386
387#[must_use]
393pub fn get_entropy_source_info() -> Option<(&'static str, EntropyQuality)> {
394 THREAD_REGISTRY.with(|registry| unsafe {
395 registry
396 .get_source()
397 .map(|source| (source.source_id(), source.quality()))
398 })
399}
400
401impl fmt::Display for EntropyQuality {
402 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403 match self {
404 Self::Hardware => write!(f, "Hardware"),
405 Self::Os => write!(f, "OS"),
406 Self::User => write!(f, "User"),
407 Self::Deterministic => write!(f, "Deterministic"),
408 }
409 }
410}
411
412impl fmt::Display for CustomEntropyConfig {
413 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
414 write!(
415 f,
416 "CustomEntropyConfig {{ min_quality: {}, max_bytes: {}, validate: {}, timeout: {}ms }}",
417 self.min_quality, self.max_bytes_per_call, self.validate_quality, self.timeout_ms
418 )
419 }
420}
421
422impl fmt::Display for CustomEntropySource {
423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424 write!(
425 f,
426 "CustomEntropySource {{ id: {}, quality: {}, config: {} }}",
427 self.source_id, self.quality, self.config
428 )
429 }
430}
431
432#[cfg(test)]
435mod tests {
436 #[cfg(feature = "alloc")]
437 use alloc::format;
438
439 use super::*;
440
441 #[allow(clippy::cast_possible_truncation)]
443 unsafe extern "C" fn test_entropy_callback(
444 dest: *mut u8,
445 len: usize,
446 _context: *mut u8,
447 ) -> i32 {
448 if dest.is_null() {
449 return -1;
450 }
451
452 for i in 0..len {
454 unsafe {
455 *dest.add(i) = (i as u8).wrapping_add(42);
456 }
457 }
458
459 0
460 }
461
462 #[test]
463 fn test_entropy_context_creation() {
464 let context = EntropyContext::empty();
465 assert!(context.user_data.is_null());
466 assert_eq!(context.size, 0);
467
468 let data = [1u8, 2, 3, 4];
469 let context = unsafe { EntropyContext::new(data.as_ptr().cast_mut(), data.len()) };
470 assert!(!context.user_data.is_null());
471 assert_eq!(context.size, 4);
472 }
473
474 #[test]
475 fn test_entropy_quality() {
476 assert!(EntropyQuality::Hardware.is_secure());
477 assert!(EntropyQuality::Os.is_secure());
478 assert!(EntropyQuality::User.is_secure());
479 assert!(!EntropyQuality::Deterministic.is_secure());
480
481 assert!((EntropyQuality::Hardware.as_f64() - 1.0).abs() < f64::EPSILON);
482 assert!((EntropyQuality::Os.as_f64() - 0.95).abs() < f64::EPSILON);
483 assert!((EntropyQuality::User.as_f64() - 0.8).abs() < f64::EPSILON);
484 assert!((EntropyQuality::Deterministic.as_f64() - 0.0).abs() < f64::EPSILON);
485 }
486
487 #[test]
488 fn test_custom_entropy_config_default() {
489 let config = CustomEntropyConfig::default();
490 assert_eq!(config.min_quality, EntropyQuality::User);
491 assert_eq!(config.max_bytes_per_call, 1024);
492 assert!(config.validate_quality);
493 assert_eq!(config.timeout_ms, 1000);
494 }
495
496 #[test]
497 fn test_custom_entropy_source_creation() {
498 let context = EntropyContext::empty();
499 let config = CustomEntropyConfig::default();
500
501 let source = unsafe {
502 CustomEntropySource::new(
503 test_entropy_callback,
504 context,
505 EntropyQuality::User,
506 config,
507 "test_source",
508 )
509 };
510
511 assert_eq!(source.source_id(), "test_source");
512 assert_eq!(source.quality(), EntropyQuality::User);
513 }
514
515 #[test]
516 #[allow(clippy::cast_possible_truncation)]
517 fn test_custom_entropy_generation() {
518 let context = EntropyContext::empty();
519 let config = CustomEntropyConfig::default();
520
521 let source = unsafe {
522 CustomEntropySource::new(
523 test_entropy_callback,
524 context,
525 EntropyQuality::User,
526 config,
527 "test_source",
528 )
529 };
530
531 let mut buffer = [0u8; 16];
532 source.generate_entropy(&mut buffer).unwrap();
533
534 for (i, &byte) in buffer.iter().enumerate() {
536 let expected = (i as u8).wrapping_add(42);
537 assert_eq!(byte, expected);
538 }
539 }
540
541 #[test]
542 fn test_custom_entropy_max_bytes_validation() {
543 let context = EntropyContext::empty();
544 let config = CustomEntropyConfig {
545 max_bytes_per_call: 8,
546 ..Default::default()
547 };
548
549 let source = unsafe {
550 CustomEntropySource::new(
551 test_entropy_callback,
552 context,
553 EntropyQuality::User,
554 config,
555 "test_source",
556 )
557 };
558
559 let mut buffer = [0u8; 16]; let result = source.generate_entropy(&mut buffer);
561 assert!(result.is_err());
562 }
563
564 #[test]
565 fn test_custom_entropy_quality_validation() {
566 let context = EntropyContext::empty();
567 let config = CustomEntropyConfig {
568 validate_quality: true,
569 ..Default::default()
570 };
571
572 let source = unsafe {
573 CustomEntropySource::new(
574 test_entropy_callback,
575 context,
576 EntropyQuality::Deterministic, config,
578 "test_source",
579 )
580 };
581
582 let mut buffer = [0u8; 8];
583 let result = source.generate_entropy(&mut buffer);
584 assert!(result.is_err());
585 }
586
587 #[test]
588 fn test_thread_entropy_registry() {
589 let _registry = ThreadEntropyRegistry::new();
590
591 assert!(!has_custom_entropy_source());
593 assert!(get_entropy_source_info().is_none());
594
595 let context = EntropyContext::empty();
596 let config = CustomEntropyConfig::default();
597 let source = CustomEntropySource {
598 callback: test_entropy_callback,
599 context,
600 quality: EntropyQuality::User,
601 config,
602 source_id: "test_registry",
603 };
604
605 unsafe {
607 register_custom_entropy_source(&raw const source);
608 }
609
610 assert!(has_custom_entropy_source());
611 let info = get_entropy_source_info().unwrap();
612 assert_eq!(info.0, "test_registry");
613 assert_eq!(info.1, EntropyQuality::User);
614
615 let mut buffer = [0u8; 8];
617 generate_custom_entropy(&mut buffer).unwrap();
618
619 unregister_custom_entropy_source();
621 assert!(!has_custom_entropy_source());
622 assert!(get_entropy_source_info().is_none());
623 }
624
625 #[test]
626 #[cfg(feature = "alloc")]
627 fn test_entropy_source_display() {
628 let context = EntropyContext::empty();
629 let config = CustomEntropyConfig::default();
630
631 let source = unsafe {
632 CustomEntropySource::new(
633 test_entropy_callback,
634 context,
635 EntropyQuality::Hardware,
636 config,
637 "display_test",
638 )
639 };
640
641 let display = format!("{source}");
642 assert!(display.contains("display_test"));
643 assert!(display.contains("Hardware"));
644 }
645}