Skip to main content

lib_q_random/
custom_entropy.rs

1//! Custom Entropy Source System for `no_std` and WASM Environments
2//!
3//! This module provides a secure, callback-based entropy source system that allows
4//! developers to plug in custom entropy sources for `no_std` and WASM environments.
5//! The system uses function pointers and thread-local storage to avoid global state
6//! while maintaining security and performance.
7
8use core::sync::atomic::{
9    AtomicPtr,
10    Ordering,
11};
12use core::{
13    fmt,
14    ptr,
15};
16
17use crate::{
18    Error,
19    Result,
20};
21
22/// Function pointer type for custom entropy sources
23///
24/// This function should fill the provided buffer with cryptographically secure
25/// random bytes. The function must be thread-safe and should not block indefinitely.
26///
27/// # Arguments
28///
29/// * `dest` - Buffer to fill with random bytes
30/// * `len` - Number of bytes to generate
31/// * `context` - Optional context data passed to the entropy source
32///
33/// # Returns
34///
35/// Returns `Ok(())` on success, or an error if entropy generation fails.
36///
37/// # Safety
38///
39/// The `dest` pointer must be valid for `len` bytes and must not be null.
40/// The function must not cause undefined behavior.
41pub type EntropyCallback = unsafe extern "C" fn(dest: *mut u8, len: usize, context: *mut u8) -> i32;
42
43/// Context data for entropy callbacks
44///
45/// This structure can be used to pass additional context to entropy callbacks,
46/// such as user data or configuration.
47#[derive(Debug, Clone, Copy)]
48pub struct EntropyContext {
49    /// User-defined context data
50    pub user_data: *mut u8,
51    /// Context size in bytes
52    pub size: usize,
53}
54
55impl EntropyContext {
56    /// Create a new entropy context
57    ///
58    /// # Arguments
59    ///
60    /// * `user_data` - User-defined context data
61    /// * `size` - Size of the context data in bytes
62    ///
63    /// # Safety
64    ///
65    /// The `user_data` pointer must be valid for `size` bytes if `size > 0`.
66    pub const unsafe fn new(user_data: *mut u8, size: usize) -> Self {
67        Self { user_data, size }
68    }
69
70    /// Create an empty entropy context
71    #[must_use]
72    pub const fn empty() -> Self {
73        Self {
74            user_data: ptr::null_mut(),
75            size: 0,
76        }
77    }
78}
79
80/// Entropy source quality levels
81#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
82pub enum EntropyQuality {
83    /// Hardware-based entropy source (highest quality)
84    Hardware,
85    /// OS-provided entropy source
86    Os,
87    /// User-provided entropy source
88    User,
89    /// Deterministic source (lowest quality, testing only)
90    Deterministic,
91}
92
93impl EntropyQuality {
94    /// Get the numeric quality value (0.0 to 1.0)
95    #[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    /// Check if this quality level is cryptographically secure
106    #[must_use]
107    pub fn is_secure(self) -> bool {
108        matches!(self, Self::Hardware | Self::Os | Self::User)
109    }
110}
111
112/// Custom entropy source configuration
113#[derive(Debug, Clone)]
114pub struct CustomEntropyConfig {
115    /// Minimum entropy quality required
116    pub min_quality: EntropyQuality,
117    /// Maximum bytes per entropy call
118    pub max_bytes_per_call: usize,
119    /// Whether to validate entropy quality
120    pub validate_quality: bool,
121    /// Timeout for entropy generation (in some unit)
122    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/// Custom entropy source registration
137///
138/// This structure manages the registration of custom entropy sources
139/// for the current thread.
140#[derive(Debug)]
141pub struct CustomEntropySource {
142    /// Callback function for entropy generation
143    pub callback: EntropyCallback,
144    /// Context data for the callback
145    pub context: EntropyContext,
146    /// Quality level of this entropy source
147    pub quality: EntropyQuality,
148    /// Configuration for this entropy source
149    pub config: CustomEntropyConfig,
150    /// Source identifier
151    pub source_id: &'static str,
152}
153
154impl CustomEntropySource {
155    /// Create a new custom entropy source
156    ///
157    /// # Arguments
158    ///
159    /// * `callback` - Function to call for entropy generation
160    /// * `context` - Context data for the callback
161    /// * `quality` - Quality level of this entropy source
162    /// * `config` - Configuration for this entropy source
163    /// * `source_id` - Unique identifier for this source
164    ///
165    /// # Safety
166    ///
167    /// The `callback` function must be thread-safe and must not cause
168    /// undefined behavior. The `context.user_data` must be valid for
169    /// `context.size` bytes if `context.size > 0`.
170    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    /// Get the callback function
187    #[must_use]
188    pub fn callback(&self) -> EntropyCallback {
189        self.callback
190    }
191
192    /// Get the context data
193    #[must_use]
194    pub fn context(&self) -> EntropyContext {
195        self.context
196    }
197
198    /// Get the quality level
199    #[must_use]
200    pub fn quality(&self) -> EntropyQuality {
201        self.quality
202    }
203
204    /// Get the configuration
205    #[must_use]
206    pub fn config(&self) -> &CustomEntropyConfig {
207        &self.config
208    }
209
210    /// Get the source identifier
211    #[must_use]
212    pub fn source_id(&self) -> &'static str {
213        self.source_id
214    }
215
216    /// Generate entropy using this source
217    ///
218    /// # Arguments
219    ///
220    /// * `dest` - Buffer to fill with random bytes
221    ///
222    /// # Errors
223    ///
224    /// Returns an error if entropy generation fails or if the generated
225    /// entropy doesn't meet quality requirements.
226    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        // Call the custom entropy function
243        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
257/// Thread-local entropy source registry
258///
259/// This structure manages custom entropy sources for the current thread.
260/// It uses atomic operations to ensure thread safety.
261pub struct ThreadEntropyRegistry {
262    /// Currently registered entropy source
263    source: AtomicPtr<CustomEntropySource>,
264}
265
266impl Default for ThreadEntropyRegistry {
267    fn default() -> Self {
268        Self::new()
269    }
270}
271
272impl ThreadEntropyRegistry {
273    /// Create a new thread entropy registry
274    #[must_use]
275    pub const fn new() -> Self {
276        Self {
277            source: AtomicPtr::new(ptr::null_mut()),
278        }
279    }
280
281    /// Register a custom entropy source for this thread
282    ///
283    /// # Arguments
284    ///
285    /// * `source` - The custom entropy source to register
286    ///
287    /// # Safety
288    ///
289    /// The `source` must remain valid for the lifetime of the registry.
290    /// The caller is responsible for ensuring the source is not dropped
291    /// while registered.
292    pub unsafe fn register(&self, source: *const CustomEntropySource) {
293        self.source.store(source.cast_mut(), Ordering::Release);
294    }
295
296    /// Unregister the current entropy source
297    pub fn unregister(&self) {
298        self.source.store(ptr::null_mut(), Ordering::Release);
299    }
300
301    /// Get the currently registered entropy source
302    ///
303    /// # Returns
304    ///
305    /// Returns a reference to the registered entropy source, or `None`
306    /// if no source is registered.
307    ///
308    /// # Safety
309    ///
310    /// The returned reference is only valid as long as the source remains
311    /// registered and not dropped.
312    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    /// Generate entropy using the registered source
322    ///
323    /// # Arguments
324    ///
325    /// * `dest` - Buffer to fill with random bytes
326    ///
327    /// # Errors
328    ///
329    /// Returns an error if no source is registered or if entropy generation fails.
330    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
348/// Register a custom entropy source for the current thread
349///
350/// # Arguments
351///
352/// * `source` - The custom entropy source to register
353///
354/// # Safety
355///
356/// The `source` must remain valid for the lifetime of the registration.
357/// The caller is responsible for ensuring the source is not dropped
358/// while registered.
359pub unsafe fn register_custom_entropy_source(source: *const CustomEntropySource) {
360    THREAD_REGISTRY.with(|registry| unsafe { registry.register(source) });
361}
362
363/// Unregister the current custom entropy source
364pub fn unregister_custom_entropy_source() {
365    THREAD_REGISTRY.with(ThreadEntropyRegistry::unregister);
366}
367
368/// Generate entropy using the registered custom source
369///
370/// # Arguments
371///
372/// * `dest` - Buffer to fill with random bytes
373///
374/// # Errors
375///
376/// Returns an error if no source is registered or if entropy generation fails.
377pub fn generate_custom_entropy(dest: &mut [u8]) -> Result<()> {
378    THREAD_REGISTRY.with(|registry| registry.generate_entropy(dest))
379}
380
381/// Check if a custom entropy source is registered
382#[must_use]
383pub fn has_custom_entropy_source() -> bool {
384    THREAD_REGISTRY.with(|registry| unsafe { registry.get_source().is_some() })
385}
386
387/// Get information about the registered entropy source
388///
389/// # Returns
390///
391/// Returns a tuple of (`source_id`, quality) if a source is registered.
392#[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// CustomEntropySource is not an RNG itself, it's a source of entropy for RNGs
433
434#[cfg(test)]
435mod tests {
436    #[cfg(feature = "alloc")]
437    use alloc::format;
438
439    use super::*;
440
441    // Test entropy callback that generates predictable data
442    #[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        // Generate predictable test data (handle empty buffer case)
453        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        // Check that the callback was called (predictable test data)
535        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]; // Exceeds max_bytes_per_call
560        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, // Low quality
577                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        // Initially no source registered
592        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        // Register the source
606        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        // Test entropy generation
616        let mut buffer = [0u8; 8];
617        generate_custom_entropy(&mut buffer).unwrap();
618
619        // Unregister the source
620        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}