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
344static THREAD_REGISTRY: ThreadEntropyRegistry = ThreadEntropyRegistry::new();
346
347pub unsafe fn register_custom_entropy_source(source: *const CustomEntropySource) {
359 unsafe { THREAD_REGISTRY.register(source) };
360}
361
362pub fn unregister_custom_entropy_source() {
364 THREAD_REGISTRY.unregister();
365}
366
367pub fn generate_custom_entropy(dest: &mut [u8]) -> Result<()> {
377 THREAD_REGISTRY.generate_entropy(dest)
378}
379
380pub fn has_custom_entropy_source() -> bool {
382 unsafe { THREAD_REGISTRY.get_source().is_some() }
383}
384
385pub fn get_entropy_source_info() -> Option<(&'static str, EntropyQuality)> {
391 unsafe {
392 THREAD_REGISTRY
393 .get_source()
394 .map(|source| (source.source_id(), source.quality()))
395 }
396}
397
398impl fmt::Display for EntropyQuality {
399 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400 match self {
401 Self::Hardware => write!(f, "Hardware"),
402 Self::Os => write!(f, "OS"),
403 Self::User => write!(f, "User"),
404 Self::Deterministic => write!(f, "Deterministic"),
405 }
406 }
407}
408
409impl fmt::Display for CustomEntropyConfig {
410 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411 write!(
412 f,
413 "CustomEntropyConfig {{ min_quality: {}, max_bytes: {}, validate: {}, timeout: {}ms }}",
414 self.min_quality, self.max_bytes_per_call, self.validate_quality, self.timeout_ms
415 )
416 }
417}
418
419impl fmt::Display for CustomEntropySource {
420 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421 write!(
422 f,
423 "CustomEntropySource {{ id: {}, quality: {}, config: {} }}",
424 self.source_id, self.quality, self.config
425 )
426 }
427}
428
429#[cfg(test)]
432mod tests {
433 #[cfg(feature = "alloc")]
434 use alloc::format;
435
436 use super::*;
437
438 #[allow(clippy::cast_possible_truncation)]
440 unsafe extern "C" fn test_entropy_callback(
441 dest: *mut u8,
442 len: usize,
443 _context: *mut u8,
444 ) -> i32 {
445 if dest.is_null() {
446 return -1;
447 }
448
449 for i in 0..len {
451 unsafe {
452 *dest.add(i) = (i as u8).wrapping_add(42);
453 }
454 }
455
456 0
457 }
458
459 #[test]
460 fn test_entropy_context_creation() {
461 let context = EntropyContext::empty();
462 assert!(context.user_data.is_null());
463 assert_eq!(context.size, 0);
464
465 let data = [1u8, 2, 3, 4];
466 let context = unsafe { EntropyContext::new(data.as_ptr().cast_mut(), data.len()) };
467 assert!(!context.user_data.is_null());
468 assert_eq!(context.size, 4);
469 }
470
471 #[test]
472 fn test_entropy_quality() {
473 assert!(EntropyQuality::Hardware.is_secure());
474 assert!(EntropyQuality::Os.is_secure());
475 assert!(EntropyQuality::User.is_secure());
476 assert!(!EntropyQuality::Deterministic.is_secure());
477
478 assert!((EntropyQuality::Hardware.as_f64() - 1.0).abs() < f64::EPSILON);
479 assert!((EntropyQuality::Os.as_f64() - 0.95).abs() < f64::EPSILON);
480 assert!((EntropyQuality::User.as_f64() - 0.8).abs() < f64::EPSILON);
481 assert!((EntropyQuality::Deterministic.as_f64() - 0.0).abs() < f64::EPSILON);
482 }
483
484 #[test]
485 fn test_custom_entropy_config_default() {
486 let config = CustomEntropyConfig::default();
487 assert_eq!(config.min_quality, EntropyQuality::User);
488 assert_eq!(config.max_bytes_per_call, 1024);
489 assert!(config.validate_quality);
490 assert_eq!(config.timeout_ms, 1000);
491 }
492
493 #[test]
494 fn test_custom_entropy_source_creation() {
495 let context = EntropyContext::empty();
496 let config = CustomEntropyConfig::default();
497
498 let source = unsafe {
499 CustomEntropySource::new(
500 test_entropy_callback,
501 context,
502 EntropyQuality::User,
503 config,
504 "test_source",
505 )
506 };
507
508 assert_eq!(source.source_id(), "test_source");
509 assert_eq!(source.quality(), EntropyQuality::User);
510 }
511
512 #[test]
513 #[allow(clippy::cast_possible_truncation)]
514 fn test_custom_entropy_generation() {
515 let context = EntropyContext::empty();
516 let config = CustomEntropyConfig::default();
517
518 let source = unsafe {
519 CustomEntropySource::new(
520 test_entropy_callback,
521 context,
522 EntropyQuality::User,
523 config,
524 "test_source",
525 )
526 };
527
528 let mut buffer = [0u8; 16];
529 source.generate_entropy(&mut buffer).unwrap();
530
531 for (i, &byte) in buffer.iter().enumerate() {
533 let expected = (i as u8).wrapping_add(42);
534 assert_eq!(byte, expected);
535 }
536 }
537
538 #[test]
539 fn test_custom_entropy_max_bytes_validation() {
540 let context = EntropyContext::empty();
541 let config = CustomEntropyConfig {
542 max_bytes_per_call: 8,
543 ..Default::default()
544 };
545
546 let source = unsafe {
547 CustomEntropySource::new(
548 test_entropy_callback,
549 context,
550 EntropyQuality::User,
551 config,
552 "test_source",
553 )
554 };
555
556 let mut buffer = [0u8; 16]; let result = source.generate_entropy(&mut buffer);
558 assert!(result.is_err());
559 }
560
561 #[test]
562 fn test_custom_entropy_quality_validation() {
563 let context = EntropyContext::empty();
564 let config = CustomEntropyConfig {
565 validate_quality: true,
566 ..Default::default()
567 };
568
569 let source = unsafe {
570 CustomEntropySource::new(
571 test_entropy_callback,
572 context,
573 EntropyQuality::Deterministic, config,
575 "test_source",
576 )
577 };
578
579 let mut buffer = [0u8; 8];
580 let result = source.generate_entropy(&mut buffer);
581 assert!(result.is_err());
582 }
583
584 #[test]
585 fn test_thread_entropy_registry() {
586 let _registry = ThreadEntropyRegistry::new();
587
588 assert!(!has_custom_entropy_source());
590 assert!(get_entropy_source_info().is_none());
591
592 let context = EntropyContext::empty();
593 let config = CustomEntropyConfig::default();
594 let source = CustomEntropySource {
595 callback: test_entropy_callback,
596 context,
597 quality: EntropyQuality::User,
598 config,
599 source_id: "test_registry",
600 };
601
602 unsafe {
604 register_custom_entropy_source(&raw const source);
605 }
606
607 assert!(has_custom_entropy_source());
608 let info = get_entropy_source_info().unwrap();
609 assert_eq!(info.0, "test_registry");
610 assert_eq!(info.1, EntropyQuality::User);
611
612 let mut buffer = [0u8; 8];
614 generate_custom_entropy(&mut buffer).unwrap();
615
616 unregister_custom_entropy_source();
618 assert!(!has_custom_entropy_source());
619 assert!(get_entropy_source_info().is_none());
620 }
621
622 #[test]
623 #[cfg(feature = "alloc")]
624 fn test_entropy_source_display() {
625 let context = EntropyContext::empty();
626 let config = CustomEntropyConfig::default();
627
628 let source = unsafe {
629 CustomEntropySource::new(
630 test_entropy_callback,
631 context,
632 EntropyQuality::Hardware,
633 config,
634 "display_test",
635 )
636 };
637
638 let display = format!("{source}");
639 assert!(display.contains("display_test"));
640 assert!(display.contains("Hardware"));
641 }
642}