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
344#[cfg(feature = "std")]
346thread_local! {
347 static THREAD_REGISTRY: ThreadEntropyRegistry = const { ThreadEntropyRegistry::new() };
348}
349
350#[cfg(not(feature = "std"))]
351static REGISTRY: ThreadEntropyRegistry = const { ThreadEntropyRegistry::new() };
352
353#[inline]
354fn with_registry<F, R>(f: F) -> R
355where
356 F: FnOnce(&ThreadEntropyRegistry) -> R,
357{
358 #[cfg(feature = "std")]
359 {
360 THREAD_REGISTRY.with(f)
361 }
362 #[cfg(not(feature = "std"))]
363 {
364 f(®ISTRY)
365 }
366}
367
368pub unsafe fn register_custom_entropy_source(source: *const CustomEntropySource) {
380 with_registry(|registry| unsafe { registry.register(source) });
381}
382
383pub fn unregister_custom_entropy_source() {
385 with_registry(ThreadEntropyRegistry::unregister);
386}
387
388pub fn generate_custom_entropy(dest: &mut [u8]) -> Result<()> {
398 with_registry(|registry| registry.generate_entropy(dest))
399}
400
401#[must_use]
403pub fn has_custom_entropy_source() -> bool {
404 with_registry(|registry| unsafe { registry.get_source().is_some() })
405}
406
407#[must_use]
413pub fn get_entropy_source_info() -> Option<(&'static str, EntropyQuality)> {
414 with_registry(|registry| unsafe {
415 registry
416 .get_source()
417 .map(|source| (source.source_id(), source.quality()))
418 })
419}
420
421impl fmt::Display for EntropyQuality {
422 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423 match self {
424 Self::Hardware => write!(f, "Hardware"),
425 Self::Os => write!(f, "OS"),
426 Self::User => write!(f, "User"),
427 Self::Deterministic => write!(f, "Deterministic"),
428 }
429 }
430}
431
432impl fmt::Display for CustomEntropyConfig {
433 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
434 write!(
435 f,
436 "CustomEntropyConfig {{ min_quality: {}, max_bytes: {}, validate: {}, timeout: {}ms }}",
437 self.min_quality, self.max_bytes_per_call, self.validate_quality, self.timeout_ms
438 )
439 }
440}
441
442impl fmt::Display for CustomEntropySource {
443 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
444 write!(
445 f,
446 "CustomEntropySource {{ id: {}, quality: {}, config: {} }}",
447 self.source_id, self.quality, self.config
448 )
449 }
450}
451
452#[cfg(test)]
455mod tests {
456 #[cfg(feature = "alloc")]
457 use alloc::format;
458
459 use super::*;
460
461 #[allow(clippy::cast_possible_truncation)]
463 unsafe extern "C" fn test_entropy_callback(
464 dest: *mut u8,
465 len: usize,
466 _context: *mut u8,
467 ) -> i32 {
468 if dest.is_null() {
469 return -1;
470 }
471
472 for i in 0..len {
474 unsafe {
475 *dest.add(i) = (i as u8).wrapping_add(42);
476 }
477 }
478
479 0
480 }
481
482 #[test]
483 fn test_entropy_context_creation() {
484 let context = EntropyContext::empty();
485 assert!(context.user_data.is_null());
486 assert_eq!(context.size, 0);
487
488 let data = [1u8, 2, 3, 4];
489 let context = unsafe { EntropyContext::new(data.as_ptr().cast_mut(), data.len()) };
490 assert!(!context.user_data.is_null());
491 assert_eq!(context.size, 4);
492 }
493
494 #[test]
495 fn test_entropy_quality() {
496 assert!(EntropyQuality::Hardware.is_secure());
497 assert!(EntropyQuality::Os.is_secure());
498 assert!(EntropyQuality::User.is_secure());
499 assert!(!EntropyQuality::Deterministic.is_secure());
500
501 assert!((EntropyQuality::Hardware.as_f64() - 1.0).abs() < f64::EPSILON);
502 assert!((EntropyQuality::Os.as_f64() - 0.95).abs() < f64::EPSILON);
503 assert!((EntropyQuality::User.as_f64() - 0.8).abs() < f64::EPSILON);
504 assert!((EntropyQuality::Deterministic.as_f64() - 0.0).abs() < f64::EPSILON);
505 }
506
507 #[test]
508 fn test_custom_entropy_config_default() {
509 let config = CustomEntropyConfig::default();
510 assert_eq!(config.min_quality, EntropyQuality::User);
511 assert_eq!(config.max_bytes_per_call, 1024);
512 assert!(config.validate_quality);
513 assert_eq!(config.timeout_ms, 1000);
514 }
515
516 #[test]
517 fn test_custom_entropy_source_creation() {
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 assert_eq!(source.source_id(), "test_source");
532 assert_eq!(source.quality(), EntropyQuality::User);
533 }
534
535 #[test]
536 #[allow(clippy::cast_possible_truncation)]
537 fn test_custom_entropy_generation() {
538 let context = EntropyContext::empty();
539 let config = CustomEntropyConfig::default();
540
541 let source = unsafe {
542 CustomEntropySource::new(
543 test_entropy_callback,
544 context,
545 EntropyQuality::User,
546 config,
547 "test_source",
548 )
549 };
550
551 let mut buffer = [0u8; 16];
552 source.generate_entropy(&mut buffer).unwrap();
553
554 for (i, &byte) in buffer.iter().enumerate() {
556 let expected = (i as u8).wrapping_add(42);
557 assert_eq!(byte, expected);
558 }
559 }
560
561 #[test]
562 fn test_custom_entropy_max_bytes_validation() {
563 let context = EntropyContext::empty();
564 let config = CustomEntropyConfig {
565 max_bytes_per_call: 8,
566 ..Default::default()
567 };
568
569 let source = unsafe {
570 CustomEntropySource::new(
571 test_entropy_callback,
572 context,
573 EntropyQuality::User,
574 config,
575 "test_source",
576 )
577 };
578
579 let mut buffer = [0u8; 16]; let result = source.generate_entropy(&mut buffer);
581 assert!(result.is_err());
582 }
583
584 #[test]
585 fn test_custom_entropy_quality_validation() {
586 let context = EntropyContext::empty();
587 let config = CustomEntropyConfig {
588 validate_quality: true,
589 ..Default::default()
590 };
591
592 let source = unsafe {
593 CustomEntropySource::new(
594 test_entropy_callback,
595 context,
596 EntropyQuality::Deterministic, config,
598 "test_source",
599 )
600 };
601
602 let mut buffer = [0u8; 8];
603 let result = source.generate_entropy(&mut buffer);
604 assert!(result.is_err());
605 }
606
607 #[test]
608 fn test_thread_entropy_registry() {
609 let _registry = ThreadEntropyRegistry::new();
610
611 assert!(!has_custom_entropy_source());
613 assert!(get_entropy_source_info().is_none());
614
615 let context = EntropyContext::empty();
616 let config = CustomEntropyConfig::default();
617 let source = CustomEntropySource {
618 callback: test_entropy_callback,
619 context,
620 quality: EntropyQuality::User,
621 config,
622 source_id: "test_registry",
623 };
624
625 unsafe {
627 register_custom_entropy_source(&raw const source);
628 }
629
630 assert!(has_custom_entropy_source());
631 let info = get_entropy_source_info().unwrap();
632 assert_eq!(info.0, "test_registry");
633 assert_eq!(info.1, EntropyQuality::User);
634
635 let mut buffer = [0u8; 8];
637 generate_custom_entropy(&mut buffer).unwrap();
638
639 unregister_custom_entropy_source();
641 assert!(!has_custom_entropy_source());
642 assert!(get_entropy_source_info().is_none());
643 }
644
645 #[test]
646 #[cfg(feature = "alloc")]
647 fn test_entropy_source_display() {
648 let context = EntropyContext::empty();
649 let config = CustomEntropyConfig::default();
650
651 let source = unsafe {
652 CustomEntropySource::new(
653 test_entropy_callback,
654 context,
655 EntropyQuality::Hardware,
656 config,
657 "display_test",
658 )
659 };
660
661 let display = format!("{source}");
662 assert!(display.contains("display_test"));
663 assert!(display.contains("Hardware"));
664 }
665}