Skip to main content

lib_q_random/
entropy.rs

1// Allow clippy warnings in entropy source code
2// These are legitimate patterns for platform-specific implementations
3#![allow(
4    clippy::must_use_candidate,
5    clippy::cast_lossless,
6    clippy::manual_clamp,
7    clippy::needless_return,
8    clippy::collapsible_if,
9    clippy::match_same_arms,
10    clippy::unreadable_literal,
11    clippy::missing_errors_doc
12)]
13
14//! Entropy source implementations
15//!
16//! This module provides various entropy source implementations for different
17//! platforms and use cases, including OS entropy, hardware RNGs, and
18//! deterministic sources for testing.
19
20#[cfg(feature = "alloc")]
21use alloc::{
22    boxed::Box,
23    vec::Vec,
24};
25
26use rand_chacha::ChaCha20Rng;
27use rand_core::{
28    Rng,
29    SeedableRng,
30};
31
32use crate::traits::{
33    EntropyConfig,
34    EntropySource,
35    EntropySourceType,
36};
37use crate::{
38    Error,
39    Result,
40};
41
42/// Operating system entropy source
43///
44/// This entropy source uses the operating system's secure random number
45/// generator, typically `/dev/urandom` on Unix-like systems or
46/// `CryptGenRandom` on Windows.
47#[derive(Debug, Clone)]
48pub struct OsEntropySource {
49    /// Platform identifier
50    platform: &'static str,
51    /// Quality estimate
52    quality: f64,
53    /// Maximum entropy per call (from config)
54    max_per_call: Option<usize>,
55}
56
57impl OsEntropySource {
58    /// Create a new OS entropy source
59    pub fn new() -> Self {
60        let platform = Self::detect_platform();
61        let quality = Self::estimate_platform_quality(platform);
62        Self {
63            platform,
64            quality,
65            max_per_call: None,
66        }
67    }
68
69    /// Estimate quality based on platform
70    fn estimate_platform_quality(platform: &'static str) -> f64 {
71        match platform {
72            "Linux" => 0.95,       // /dev/urandom is generally high quality
73            "macOS" => 0.95,       // SecRandomCopyBytes is high quality
74            "Windows" => 0.95,     // CryptGenRandom is high quality
75            "FreeBSD" => 0.95,     // /dev/urandom is high quality
76            "OpenBSD" => 0.95,     // /dev/urandom is high quality
77            "NetBSD" => 0.95,      // /dev/urandom is high quality
78            "WebAssembly" => 0.90, // Browser crypto.getRandomValues() is good but slightly lower
79            _ => 0.80,             // Unknown platforms get conservative estimate
80        }
81    }
82
83    /// Get the platform identifier
84    pub fn platform(&self) -> &'static str {
85        self.platform
86    }
87
88    /// Detect the current platform
89    fn detect_platform() -> &'static str {
90        #[cfg(target_os = "linux")]
91        return "Linux";
92        #[cfg(target_os = "macos")]
93        return "macOS";
94        #[cfg(target_os = "windows")]
95        return "Windows";
96        #[cfg(target_os = "freebsd")]
97        return "FreeBSD";
98        #[cfg(target_os = "openbsd")]
99        return "OpenBSD";
100        #[cfg(target_os = "netbsd")]
101        return "NetBSD";
102        #[cfg(target_arch = "wasm32")]
103        return "WebAssembly";
104        #[cfg(not(any(
105            target_os = "linux",
106            target_os = "macos",
107            target_os = "windows",
108            target_os = "freebsd",
109            target_os = "openbsd",
110            target_os = "netbsd",
111            target_arch = "wasm32"
112        )))]
113        return "Unknown";
114    }
115}
116
117impl EntropySource for OsEntropySource {
118    fn get_entropy(&mut self, dest: &mut [u8]) -> Result<()> {
119        // Check if the requested amount exceeds the maximum per call
120        if let Some(max_per_call) = self.max_entropy_per_call() {
121            if dest.len() > max_per_call {
122                return Err(Error::entropy_source_unavailable(
123                    "Requested entropy exceeds maximum per call",
124                ));
125            }
126        }
127
128        #[cfg(feature = "std")]
129        {
130            getrandom::fill(dest).map_err(|_| {
131                Error::platform_rng_failed_with_code(
132                    self.platform,
133                    -1,
134                    "Failed to get entropy from OS",
135                )
136            })
137        }
138        #[cfg(not(feature = "std"))]
139        {
140            Err(Error::feature_not_available("OS entropy source", &["std"]))
141        }
142    }
143
144    fn initialize(&mut self, config: &EntropyConfig) -> Result<()> {
145        // Validate that the source meets the minimum quality requirement
146        if self.quality() < config.min_quality {
147            return Err(Error::entropy_source_unavailable(
148                "OS entropy source quality below required minimum",
149            ));
150        }
151
152        // Update max_per_call if specified in config
153        if let Some(max_per_call) = config.max_per_call {
154            if max_per_call > 256 {
155                return Err(Error::entropy_source_unavailable(
156                    "Requested max_per_call exceeds OS entropy source limit",
157                ));
158            }
159            self.max_per_call = Some(max_per_call);
160        }
161
162        Ok(())
163    }
164
165    fn is_available(&self) -> bool {
166        #[cfg(feature = "std")]
167        {
168            true
169        }
170        #[cfg(not(feature = "std"))]
171        {
172            false
173        }
174    }
175
176    fn quality(&self) -> f64 {
177        self.quality
178    }
179
180    fn name(&self) -> &'static str {
181        match self.platform {
182            "Linux" => "Linux OS Entropy Source",
183            "macOS" => "macOS OS Entropy Source",
184            "Windows" => "Windows OS Entropy Source",
185            "FreeBSD" => "FreeBSD OS Entropy Source",
186            "OpenBSD" => "OpenBSD OS Entropy Source",
187            "NetBSD" => "NetBSD OS Entropy Source",
188            "WebAssembly" => "WebAssembly OS Entropy Source",
189            _ => "Unknown OS Entropy Source",
190        }
191    }
192
193    fn source_type(&self) -> EntropySourceType {
194        EntropySourceType::OperatingSystem
195    }
196
197    fn max_entropy_per_call(&self) -> Option<usize> {
198        self.max_per_call.or(Some(16384)) // Use config value or default limit (16KB)
199    }
200}
201
202impl Default for OsEntropySource {
203    fn default() -> Self {
204        Self::new()
205    }
206}
207
208/// Hardware random number generator entropy source
209///
210/// On **x86 / `x86_64`** with the **`std`** feature, this uses **RDRAND** when the
211/// CPU advertises support and a probe read succeeds. Without `std`, runtime
212/// CPUID-based detection is unavailable and this source reports as unavailable.
213///
214/// **`AArch64`** and **`PowerPC`** do not use in-process `RNDR` / `DARN` in this
215/// crate on stable Rust; use [`OsEntropySource`] for those targets.
216#[derive(Debug, Clone)]
217pub struct HardwareEntropySource {
218    /// Hardware device identifier
219    device: &'static str,
220    /// Quality estimate
221    quality: f64,
222    /// Whether hardware RNG is available
223    available: bool,
224}
225
226impl HardwareEntropySource {
227    /// Create a new hardware entropy source
228    pub fn new() -> Self {
229        let (device, available) = Self::detect_hardware_rng();
230        Self {
231            device,
232            quality: if available { 0.99 } else { 0.0 },
233            available,
234        }
235    }
236
237    /// Detect available hardware RNG (RDRAND on x86 / `x86_64` when present).
238    fn detect_hardware_rng() -> (&'static str, bool) {
239        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
240        {
241            if let Some(label) = crate::hardware_rng::probe_rdrand() {
242                return (label, true);
243            }
244            #[cfg(target_arch = "x86_64")]
245            {
246                return ("x86_64 (RDRAND unavailable)", false);
247            }
248            #[cfg(target_arch = "x86")]
249            {
250                return ("x86 (RDRAND unavailable)", false);
251            }
252        }
253        #[cfg(target_arch = "aarch64")]
254        {
255            return ("AArch64", false);
256        }
257        #[cfg(any(target_arch = "powerpc64", target_arch = "powerpc"))]
258        {
259            return ("PowerPC", false);
260        }
261        #[cfg(not(any(
262            target_arch = "x86",
263            target_arch = "x86_64",
264            target_arch = "aarch64",
265            target_arch = "powerpc64",
266            target_arch = "powerpc"
267        )))]
268        {
269            return ("Unknown", false);
270        }
271    }
272}
273
274impl EntropySource for HardwareEntropySource {
275    fn get_entropy(&mut self, dest: &mut [u8]) -> Result<()> {
276        if !self.available {
277            return Err(Error::hardware_rng_failed(self.device));
278        }
279
280        // Check if the requested amount exceeds the maximum per call
281        if let Some(max_per_call) = self.max_entropy_per_call() {
282            if dest.len() > max_per_call {
283                return Err(Error::entropy_source_unavailable(
284                    "Requested entropy exceeds hardware RNG maximum per call",
285                ));
286            }
287        }
288
289        crate::hardware_rng::fill_hw_cpu(dest, self.device)
290    }
291
292    fn initialize(&mut self, config: &EntropyConfig) -> Result<()> {
293        // Validate that the source meets the minimum quality requirement
294        if self.quality() < config.min_quality {
295            return Err(Error::entropy_source_unavailable(
296                "Hardware entropy source quality below required minimum",
297            ));
298        }
299
300        // Update max_per_call if specified in config
301        if let Some(max_per_call) = config.max_per_call {
302            if max_per_call > crate::hardware_rng::HW_RNG_MAX_PER_CALL {
303                return Err(Error::entropy_source_unavailable(
304                    "Requested max_per_call exceeds hardware RNG limit",
305                ));
306            }
307        }
308
309        Ok(())
310    }
311
312    fn is_available(&self) -> bool {
313        self.available
314    }
315
316    fn quality(&self) -> f64 {
317        self.quality
318    }
319
320    fn name(&self) -> &'static str {
321        "Hardware RNG"
322    }
323
324    fn source_type(&self) -> EntropySourceType {
325        EntropySourceType::Hardware
326    }
327
328    fn max_entropy_per_call(&self) -> Option<usize> {
329        Some(crate::hardware_rng::HW_RNG_MAX_PER_CALL)
330    }
331}
332
333impl Default for HardwareEntropySource {
334    fn default() -> Self {
335        Self::new()
336    }
337}
338
339/// Deterministic entropy source for testing
340///
341/// This entropy source provides deterministic bytes from a **256-bit** `ChaCha20`
342/// stream (`rand_chacha::ChaCha20Rng`), matching the construction used across
343/// libQ for reproducible tests and vectors.
344///
345/// **USE CASE**: This is designed for:
346/// - Unit testing with reproducible outcomes
347/// - Known Answer Tests (KAT) verification
348/// - Benchmarking with consistent inputs
349///
350/// **NOT FOR CRYPTOGRAPHIC USE**: For actual cryptographic operations,
351/// use `OsEntropySource` or other secure entropy sources. Unpredictability is
352/// only as strong as the secrecy of the 32-byte seed.
353#[derive(Debug, Clone)]
354pub struct DeterministicEntropySource {
355    /// `ChaCha20` stream generator
356    rng: ChaCha20Rng,
357    /// Quality estimate (0.0 for deterministic)
358    quality: f64,
359}
360
361impl DeterministicEntropySource {
362    /// Create a new deterministic entropy source from a 32-byte `ChaCha20` key
363    #[must_use]
364    pub fn new(seed: [u8; 32]) -> Self {
365        Self {
366            rng: ChaCha20Rng::from_seed(seed),
367            quality: 0.0, // Deterministic sources have no true entropy
368        }
369    }
370
371    fn generate_deterministic_bytes(&mut self, dest: &mut [u8]) {
372        self.rng.fill_bytes(dest);
373    }
374}
375
376impl EntropySource for DeterministicEntropySource {
377    fn get_entropy(&mut self, dest: &mut [u8]) -> Result<()> {
378        self.generate_deterministic_bytes(dest);
379        Ok(())
380    }
381
382    fn initialize(&mut self, config: &EntropyConfig) -> Result<()> {
383        // For deterministic sources, we don't enforce quality requirements
384        // since they're meant for testing and have 0.0 quality by design
385        let _ = config;
386        Ok(())
387    }
388
389    fn is_available(&self) -> bool {
390        true
391    }
392
393    fn quality(&self) -> f64 {
394        self.quality
395    }
396
397    fn name(&self) -> &'static str {
398        "Deterministic Entropy Source"
399    }
400
401    fn source_type(&self) -> EntropySourceType {
402        EntropySourceType::Deterministic
403    }
404
405    fn max_entropy_per_call(&self) -> Option<usize> {
406        None // No limit for deterministic sources
407    }
408}
409
410/// User-provided entropy source
411///
412/// This entropy source allows users to provide their own entropy data,
413/// useful for specialized applications or when integrating with external
414/// entropy sources.
415#[cfg(feature = "alloc")]
416#[derive(Debug, Clone)]
417pub struct UserEntropySource {
418    /// User-provided entropy data
419    entropy_data: Vec<u8>,
420    /// Current position in the entropy data
421    position: usize,
422    /// Quality estimate
423    quality: f64,
424    /// Configured maximum entropy per call
425    max_per_call: Option<usize>,
426}
427
428#[cfg(feature = "alloc")]
429impl UserEntropySource {
430    /// Create a new user entropy source
431    pub fn new(entropy_data: Vec<u8>) -> Self {
432        Self {
433            quality: 0.8, // Assume reasonable quality for user-provided data
434            entropy_data,
435            position: 0,
436            max_per_call: None, // No limit by default
437        }
438    }
439
440    /// Create a new user entropy source with quality assessment
441    pub fn with_quality(entropy_data: Vec<u8>, quality: f64) -> Self {
442        Self {
443            quality: quality.max(0.0).min(1.0),
444            entropy_data,
445            position: 0,
446            max_per_call: None, // No limit by default
447        }
448    }
449}
450
451#[cfg(feature = "alloc")]
452impl EntropySource for UserEntropySource {
453    fn get_entropy(&mut self, dest: &mut [u8]) -> Result<()> {
454        if self.entropy_data.is_empty() {
455            return Err(Error::entropy_source_unavailable("User entropy source"));
456        }
457
458        // Check if the requested amount exceeds the maximum per call
459        if let Some(max_per_call) = self.max_per_call {
460            if dest.len() > max_per_call {
461                return Err(Error::entropy_source_unavailable(
462                    "Requested entropy exceeds user entropy source maximum per call",
463                ));
464            }
465        }
466
467        for (i, byte) in dest.iter_mut().enumerate() {
468            let index = (self.position + i) % self.entropy_data.len();
469            *byte = self.entropy_data[index];
470        }
471
472        self.position = (self.position + dest.len()) % self.entropy_data.len();
473        Ok(())
474    }
475
476    fn initialize(&mut self, config: &EntropyConfig) -> Result<()> {
477        // Validate that the source meets the minimum quality requirement
478        if self.quality() < config.min_quality {
479            return Err(Error::entropy_source_unavailable(
480                "User entropy source quality below required minimum",
481            ));
482        }
483
484        // For user entropy sources, we allow max_per_call to be larger than the entropy data size
485        // because the source can cycle through the data. We only enforce reasonable limits.
486        if let Some(max_per_call) = config.max_per_call {
487            if max_per_call > 1024 {
488                return Err(Error::entropy_source_unavailable(
489                    "Requested max_per_call exceeds reasonable limit for user entropy source",
490                ));
491            }
492            // Store the configured max_per_call
493            self.max_per_call = Some(max_per_call);
494        }
495
496        Ok(())
497    }
498
499    fn is_available(&self) -> bool {
500        !self.entropy_data.is_empty()
501    }
502
503    fn quality(&self) -> f64 {
504        self.quality
505    }
506
507    fn name(&self) -> &'static str {
508        "User Entropy Source"
509    }
510
511    fn source_type(&self) -> EntropySourceType {
512        EntropySourceType::User
513    }
514
515    fn max_entropy_per_call(&self) -> Option<usize> {
516        Some(self.entropy_data.len())
517    }
518}
519
520/// NIST AES256-CTR-DRBG entropy source for KAT test compatibility (minimal implementation).
521///
522/// This entropy source implements a minimal NIST AES256-CTR-DRBG-style algorithm:
523/// `randombytes_init` and `EntropySource` for contexts that need the same 48-byte-init
524/// API (e.g. KAT or legacy tests) but do not require reseed, personalization, or health tests.
525/// It is **not** a full NIST-aligned DRBG (no reseed interval enforcement, no uninstantiate,
526/// no health test).
527///
528/// For full NIST SP 800-90A Rev. 1 `CTR_DRBG` alignment (instantiate with personalization,
529/// reseed, uninstantiate, health test, reseed interval, error state), use the canonical
530/// implementation in **lib-q-cb-kem** behind the `nist-aes-rng` feature (`AesState`).
531#[cfg(feature = "nist-drbg")]
532#[derive(Debug, Clone)]
533pub struct NistAes256CtrDrbg {
534    /// 256-bit AES key
535    key: [u8; 32],
536    /// 128-bit counter value
537    v: [u8; 16],
538    /// Reseed counter
539    reseed_counter: i32,
540    /// Quality estimate (high for NIST DRBG)
541    quality: f64,
542}
543
544#[cfg(feature = "nist-drbg")]
545impl NistAes256CtrDrbg {
546    /// Create a new NIST AES256-CTR-DRBG entropy source
547    pub fn new() -> Self {
548        Self {
549            key: [0u8; 32],
550            v: [0u8; 16],
551            reseed_counter: 0,
552            quality: 1.0, // NIST DRBG provides high quality entropy
553        }
554    }
555}
556
557#[cfg(feature = "nist-drbg")]
558impl Default for NistAes256CtrDrbg {
559    fn default() -> Self {
560        Self::new()
561    }
562}
563
564#[cfg(feature = "nist-drbg")]
565impl NistAes256CtrDrbg {
566    /// Initialize the DRBG with entropy input (48 bytes)
567    ///
568    /// This corresponds to the `randombytes_init` function in the reference implementation.
569    pub fn randombytes_init(&mut self, entropy_input: [u8; 48]) {
570        self.key = [0u8; 32];
571        self.v = [0u8; 16];
572        self.reseed_counter = 1i32;
573
574        Self::aes256_ctr_update(&mut Some(entropy_input), &mut self.key, &mut self.v);
575        self.reseed_counter = 1;
576    }
577
578    /// AES256-ECB encryption
579    ///
580    /// Encrypts a 128-bit plaintext using AES256-ECB mode.
581    fn aes256_ecb(key: &[u8; 32], ctr: &[u8; 16], buffer: &mut [u8; 16]) {
582        use aes::cipher::{
583            BlockCipherEncrypt,
584            KeyInit,
585        };
586        use aes::{
587            Aes256,
588            Block,
589        };
590        let cipher = Aes256::new_from_slice(key).expect("32-byte key");
591        buffer.copy_from_slice(ctr);
592        let mut block = Block::from(*buffer);
593        cipher.encrypt_block(&mut block);
594        *buffer = block.into();
595    }
596
597    /// Update key and v with provided data by running one round of AES in counter mode
598    fn aes256_ctr_update(
599        provided_data: &mut Option<[u8; 48]>,
600        key: &mut [u8; 32],
601        v: &mut [u8; 16],
602    ) {
603        let mut temp = [[0u8; 16]; 3];
604
605        for tmp in &mut temp[0..3] {
606            let count = u128::from_be_bytes(*v);
607            let count_next = count.wrapping_add(1);
608            v.copy_from_slice(&count_next.to_be_bytes());
609
610            Self::aes256_ecb(key, v, tmp);
611        }
612
613        if let Some(d) = provided_data {
614            for j in 0..3 {
615                for i in 0..16 {
616                    temp[j][i] ^= d[16 * j + i];
617                }
618            }
619        }
620
621        key[0..16].copy_from_slice(&temp[0]);
622        key[16..32].copy_from_slice(&temp[1]);
623        v.copy_from_slice(&temp[2]);
624    }
625
626    /// Generate random bytes using the DRBG
627    fn generate_bytes(&mut self, dest: &mut [u8]) {
628        for chunk in dest.chunks_mut(16) {
629            let count = u128::from_be_bytes(self.v);
630            let count_next = count.wrapping_add(1);
631            self.v.copy_from_slice(&count_next.to_be_bytes());
632
633            let mut block = [0u8; 16];
634            Self::aes256_ecb(&self.key, &self.v, &mut block);
635
636            chunk.copy_from_slice(&block[..chunk.len()]);
637        }
638
639        Self::aes256_ctr_update(&mut None, &mut self.key, &mut self.v);
640        self.reseed_counter += 1;
641    }
642}
643
644#[cfg(feature = "nist-drbg")]
645impl EntropySource for NistAes256CtrDrbg {
646    fn get_entropy(&mut self, dest: &mut [u8]) -> Result<()> {
647        self.generate_bytes(dest);
648        Ok(())
649    }
650
651    fn is_available(&self) -> bool {
652        true
653    }
654
655    fn quality(&self) -> f64 {
656        self.quality
657    }
658
659    fn name(&self) -> &'static str {
660        "NIST AES256-CTR-DRBG"
661    }
662
663    fn source_type(&self) -> EntropySourceType {
664        EntropySourceType::Deterministic
665    }
666
667    fn max_entropy_per_call(&self) -> Option<usize> {
668        None // No limit for DRBG
669    }
670}
671
672/// Entropy source factory
673///
674/// This factory provides convenient methods for creating different types
675/// of entropy sources based on requirements and platform capabilities.
676#[cfg(feature = "alloc")]
677pub struct EntropySourceFactory;
678
679#[cfg(feature = "alloc")]
680impl EntropySourceFactory {
681    /// Create the best available entropy source
682    ///
683    /// This method attempts to create the highest quality entropy source
684    /// available on the current platform.
685    pub fn create_best_available() -> Result<Box<dyn EntropySource>> {
686        Self::create_best_available_with_config(&EntropyConfig::default())
687    }
688
689    /// Create the best available entropy source with configuration
690    ///
691    /// This method attempts to create the highest quality entropy source
692    /// available on the current platform that meets the specified requirements.
693    pub fn create_best_available_with_config(
694        config: &EntropyConfig,
695    ) -> Result<Box<dyn EntropySource>> {
696        // Try hardware RNG first
697        let mut hardware_source = HardwareEntropySource::new();
698        if hardware_source.is_available() {
699            if hardware_source.initialize(config).is_ok() {
700                return Ok(Box::new(hardware_source));
701            }
702        }
703
704        // Fall back to OS entropy
705        let mut os_source = OsEntropySource::new();
706        if os_source.is_available() {
707            if os_source.initialize(config).is_ok() {
708                return Ok(Box::new(os_source));
709            }
710        }
711
712        // Do not substitute non-cryptographic "fallback" RNGs when OS/hardware sources fail.
713        // Callers must fix the environment, enable `getrandom`, or register a custom entropy
714        // source (`custom-entropy` feature) for `no_std`/WASM.
715        Err(Error::entropy_source_unavailable_with_context(
716            "secure entropy",
717            "no OS or hardware entropy source satisfied the configuration; weak fallbacks are disabled",
718        ))
719    }
720
721    /// Create an OS entropy source
722    pub fn create_os_entropy() -> Result<Box<dyn EntropySource>> {
723        Self::create_os_entropy_with_config(&EntropyConfig::default())
724    }
725
726    /// Create an OS entropy source with configuration
727    pub fn create_os_entropy_with_config(config: &EntropyConfig) -> Result<Box<dyn EntropySource>> {
728        let mut source = OsEntropySource::new();
729        if source.is_available() {
730            source.initialize(config)?;
731            Ok(Box::new(source))
732        } else {
733            Err(Error::entropy_source_unavailable("OS entropy source"))
734        }
735    }
736
737    /// Create a hardware entropy source
738    pub fn create_hardware_entropy() -> Result<Box<dyn EntropySource>> {
739        Self::create_hardware_entropy_with_config(&EntropyConfig::default())
740    }
741
742    /// Create a hardware entropy source with configuration
743    pub fn create_hardware_entropy_with_config(
744        config: &EntropyConfig,
745    ) -> Result<Box<dyn EntropySource>> {
746        let mut source = HardwareEntropySource::new();
747        if source.is_available() {
748            source.initialize(config)?;
749            Ok(Box::new(source))
750        } else {
751            Err(Error::entropy_source_unavailable("Hardware entropy source"))
752        }
753    }
754
755    /// Create a deterministic entropy source (`ChaCha20` stream from `seed`)
756    pub fn create_deterministic_entropy(seed: [u8; 32]) -> Box<dyn EntropySource> {
757        Box::new(DeterministicEntropySource::new(seed))
758    }
759
760    /// Create a user entropy source
761    pub fn create_user_entropy(entropy_data: Vec<u8>) -> Box<dyn EntropySource> {
762        Box::new(UserEntropySource::new(entropy_data))
763    }
764
765    /// Create a user entropy source with quality assessment
766    pub fn create_user_entropy_with_quality(
767        entropy_data: Vec<u8>,
768        quality: f64,
769    ) -> Box<dyn EntropySource> {
770        Box::new(UserEntropySource::with_quality(entropy_data, quality))
771    }
772
773    /// Create a NIST AES256-CTR-DRBG entropy source
774    #[cfg(feature = "nist-drbg")]
775    pub fn create_nist_drbg_entropy(entropy_input: [u8; 48]) -> Box<dyn EntropySource> {
776        let mut drbg = NistAes256CtrDrbg::new();
777        drbg.randombytes_init(entropy_input);
778        Box::new(drbg)
779    }
780}
781
782#[cfg(test)]
783mod tests {
784    #[cfg(all(not(feature = "std"), feature = "alloc"))]
785    use alloc::format;
786    #[cfg(all(test, feature = "alloc"))]
787    use alloc::vec;
788
789    use super::*;
790
791    #[test]
792    fn test_os_entropy_source_creation() {
793        let source = OsEntropySource::new();
794        assert!(!source.name().is_empty());
795        assert_eq!(source.source_type(), EntropySourceType::OperatingSystem);
796
797        // Test that platform is detected and used in name
798        let platform = source.platform();
799        assert!(!platform.is_empty());
800        assert!(source.name().contains(platform));
801
802        // Test that quality is set based on platform
803        assert!(source.quality() > 0.0);
804        assert!(source.quality() <= 1.0);
805    }
806
807    #[test]
808    fn test_hardware_entropy_source_creation() {
809        let source = HardwareEntropySource::new();
810        assert!(!source.name().is_empty());
811        assert_eq!(source.source_type(), EntropySourceType::Hardware);
812    }
813
814    #[test]
815    #[cfg(all(feature = "std", target_arch = "x86_64"))]
816    fn test_hardware_entropy_rdrand_smoke_if_cpu_supports() {
817        if !std::arch::is_x86_feature_detected!("rdrand") {
818            return;
819        }
820        let mut source = HardwareEntropySource::new();
821        assert!(source.is_available());
822        source.initialize(&EntropyConfig::default()).unwrap();
823        let mut buf = [0u8; 64];
824        source.get_entropy(&mut buf).unwrap();
825    }
826
827    #[test]
828    fn test_deterministic_entropy_source_creation() {
829        let mut seed = [0u8; 32];
830        seed[..8].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
831        let source = DeterministicEntropySource::new(seed);
832        assert!(!source.name().is_empty());
833        assert_eq!(source.source_type(), EntropySourceType::Deterministic);
834        #[allow(clippy::float_cmp)]
835        {
836            assert_eq!(source.quality(), 0.0);
837        }
838    }
839
840    #[test]
841    fn test_deterministic_entropy_consistency() {
842        let seed = [42u8; 32];
843        let mut source1 = DeterministicEntropySource::new(seed);
844        let mut source2 = DeterministicEntropySource::new(seed);
845
846        let mut bytes1 = [0u8; 32];
847        let mut bytes2 = [0u8; 32];
848
849        source1.get_entropy(&mut bytes1).unwrap();
850        source2.get_entropy(&mut bytes2).unwrap();
851
852        assert_eq!(bytes1, bytes2);
853    }
854
855    #[test]
856    #[cfg(feature = "alloc")]
857    fn test_user_entropy_source_creation() {
858        let entropy_data = vec![1, 2, 3, 4, 5, 6, 7, 8];
859        let source = UserEntropySource::new(entropy_data);
860        assert!(!source.name().is_empty());
861        assert_eq!(source.source_type(), EntropySourceType::User);
862    }
863
864    #[test]
865    #[cfg(feature = "alloc")]
866    fn test_user_entropy_source_with_quality() {
867        let entropy_data = vec![1, 2, 3, 4, 5, 6, 7, 8];
868        let source = UserEntropySource::with_quality(entropy_data, 0.9);
869        #[allow(clippy::float_cmp)]
870        {
871            assert_eq!(source.quality(), 0.9);
872        }
873    }
874
875    #[test]
876    #[cfg(feature = "alloc")]
877    fn test_user_entropy_source_cycling() {
878        let entropy_data = vec![1, 2, 3];
879        let mut source = UserEntropySource::new(entropy_data);
880
881        // Initialize with a config that allows more bytes per call
882        let config = EntropyConfig {
883            max_per_call: Some(6), // Allow up to 6 bytes per call (same as what we're requesting)
884            ..Default::default()
885        };
886        source.initialize(&config).unwrap();
887
888        let mut bytes = [0u8; 6];
889        source.get_entropy(&mut bytes).unwrap();
890
891        // Should cycle through the entropy data
892        assert_eq!(bytes, [1, 2, 3, 1, 2, 3]);
893    }
894
895    #[test]
896    #[cfg(feature = "alloc")]
897    fn test_entropy_source_factory_deterministic() {
898        let mut seed = [0u8; 32];
899        seed[..4].copy_from_slice(&[1, 2, 3, 4]);
900        let source = EntropySourceFactory::create_deterministic_entropy(seed);
901        assert_eq!(source.source_type(), EntropySourceType::Deterministic);
902    }
903
904    #[test]
905    #[cfg(all(feature = "alloc", feature = "nist-drbg"))]
906    fn test_nist_drbg_kat_vectors() {
907        use alloc::vec::Vec;
908
909        // Test vectors from classic-mceliece reference implementation
910        const RNG_REF1: &str = "061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA19810F5392D076276EF41277C3AB6E94A4E3B7DCC104A05BB089D338BF55C72CAB375389A94BB920BD5D6DC9E7F2EC6FDE028B6F5724BB039F3652AD98DF8CE6C97013210B84BBE81388C3D141D61957C73BCDC5E5CD92525F46A2B757B03CAB5C337004A2DA35324A325713564DAE28F57ACC6DBE32A0726190BAA6B8A0A255AA1AD01E8DD569AA36D096256C420718A69D46D8DB1C6DD40606A0BE3C235BEFE623A90593F82D6A8F9F924E44E36BE87F7D26B8445966F9EE329C426C12521E85F6FD4ECD5D566BA0A3487125D79CC64";
911        const RNG_REF2: &str = "C17E034061ED5EA817C41D61636281E816F817DCF753A91D97C018FF82FBC9B1728FC66AF114B57978FB6082B70D285140B26725AA5F7BB4409820F67E2D656EDACA30B5BB12EB5249CC3809B188CF0CC95B5AE0EFE8FC5887152CB6601B4CCF9FC411894FA0C0264EB51A481D4D7074FDF065053030C8A92BFCDD06BF18C8489C38D03784FD63001830E5A385A4A37866693F5BDAB8A8A25B519DDBF2D28268601D95BEED647E430484A227C023B0297A282F06C91376433BDE5EC3ABBA8C06B830C26452EA2FA7EDEA8DCFE20EAFCF8980B3D5AECEF89DD861ACEC1F5F7CD2AE6B3CDE3C1D80A2830DD0B9E8468AFAD161981074BEB33DF1CDFF9A5214F9F0";
912
913        // Parse hex strings to bytes
914        fn hex_to_bytes(hex: &str) -> Vec<u8> {
915            let mut bytes = Vec::new();
916            let mut chars = hex.chars().peekable();
917            while let (Some(c1), Some(c2)) = (chars.next(), chars.next()) {
918                let byte = u8::from_str_radix(&format!("{c1}{c2}"), 16).unwrap();
919                bytes.push(byte);
920            }
921            bytes
922        }
923
924        let mut entropy_input = [0u8; 48];
925        for (i, byte) in entropy_input.iter_mut().enumerate().take(48) {
926            *byte = u8::try_from(i).expect("i < 48, so conversion to u8 is safe");
927        }
928
929        let mut drbg = NistAes256CtrDrbg::new();
930        drbg.randombytes_init(entropy_input);
931
932        // Test first 256 bytes
933        let mut data = [0u8; 256];
934        drbg.get_entropy(&mut data).unwrap();
935        let ref1 = hex_to_bytes(RNG_REF1);
936        assert_eq!(data.as_slice(), ref1.as_slice());
937
938        // Test second 256 bytes
939        drbg.get_entropy(&mut data).unwrap();
940        let ref2 = hex_to_bytes(RNG_REF2);
941        assert_eq!(data.as_slice(), ref2.as_slice());
942    }
943
944    #[test]
945    #[cfg(feature = "alloc")]
946    fn test_entropy_source_factory_user() {
947        let entropy_data = vec![1, 2, 3, 4, 5];
948        let source = EntropySourceFactory::create_user_entropy(entropy_data);
949        assert_eq!(source.source_type(), EntropySourceType::User);
950    }
951
952    #[test]
953    fn test_entropy_config_validation() {
954        let config = EntropyConfig {
955            min_quality: 0.9, // High quality requirement
956            max_per_call: Some(32),
957            ..Default::default()
958        };
959
960        // Test OS entropy source with high quality requirement
961        let mut os_source = OsEntropySource::new();
962        // OS entropy should meet the quality requirement (0.95 > 0.9)
963        assert!(os_source.initialize(&config).is_ok());
964
965        // Test with quality requirement too high
966        let config2 = EntropyConfig {
967            min_quality: 0.99,
968            ..config
969        };
970        let mut os_source2 = OsEntropySource::new();
971        // OS entropy should not meet this requirement (0.95 < 0.99)
972        assert!(os_source2.initialize(&config2).is_err());
973    }
974
975    #[test]
976    fn test_entropy_config_max_per_call() {
977        let config = EntropyConfig {
978            max_per_call: Some(16),
979            ..Default::default()
980        };
981
982        let mut os_source = OsEntropySource::new();
983        os_source.initialize(&config).unwrap();
984
985        // Test requesting more than max_per_call
986        let mut buffer = [0u8; 32]; // Request 32 bytes, but max is 16
987        assert!(os_source.get_entropy(&mut buffer).is_err());
988    }
989
990    #[test]
991    #[cfg(feature = "alloc")]
992    fn test_entropy_source_factory_with_config() {
993        let config = EntropyConfig {
994            min_quality: 0.8,
995            max_per_call: Some(64),
996            ..Default::default()
997        };
998
999        // This should work with default config
1000        let result = EntropySourceFactory::create_best_available_with_config(&config);
1001        // The result depends on platform capabilities, so we just check it doesn't panic
1002        let _ = result;
1003    }
1004
1005    #[test]
1006    #[cfg(any(feature = "std", feature = "alloc"))]
1007    fn test_platform_specific_error_messages() {
1008        let mut os_source = OsEntropySource::new();
1009        let platform = os_source.platform();
1010
1011        // Test quality error message
1012        let config = EntropyConfig {
1013            min_quality: 1.0, // Set to impossible value
1014            ..Default::default()
1015        };
1016
1017        let result = os_source.initialize(&config);
1018        assert!(result.is_err());
1019        if let Err(error) = result {
1020            let error_msg = format!("{error}");
1021            assert!(error_msg.contains("quality below required minimum"));
1022        }
1023
1024        // Test max_per_call error message
1025        let config2 = EntropyConfig {
1026            max_per_call: Some(1000), // Exceeds limit
1027            ..Default::default()
1028        };
1029
1030        let result2 = os_source.initialize(&config2);
1031        assert!(result2.is_err());
1032        if let Err(error) = result2 {
1033            let error_msg = format!("{error}");
1034            assert!(error_msg.contains("exceeds OS entropy source limit"));
1035        }
1036
1037        // Test that platform is still accessible and used in name
1038        assert!(!platform.is_empty());
1039        assert!(os_source.name().contains(platform));
1040    }
1041}