Skip to main content

openentropy_core/
source.rs

1//! Abstract entropy source trait and runtime state.
2//!
3//! Every entropy source implements the [`EntropySource`] trait, which provides
4//! metadata via [`SourceInfo`], availability checking, and raw sample collection.
5
6use std::time::Duration;
7
8/// Category of entropy source based on physical mechanism.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum SourceCategory {
11    /// Thermal noise in circuits/oscillators.
12    Thermal,
13    /// CPU/memory timing jitter.
14    Timing,
15    /// OS scheduler nondeterminism.
16    Scheduling,
17    /// Storage/peripheral latency variance.
18    IO,
19    /// Inter-process/kernel communication jitter.
20    IPC,
21    /// CPU microarchitecture race conditions.
22    Microarch,
23    /// Graphics pipeline nondeterminism.
24    GPU,
25    /// Network timing/signal noise.
26    Network,
27    /// OS counters/state.
28    System,
29    /// Signal processing entropy.
30    Signal,
31    /// Hardware sensor readings.
32    Sensor,
33    /// True quantum random number generators.
34    Quantum,
35}
36
37impl std::fmt::Display for SourceCategory {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        match self {
40            Self::Thermal => write!(f, "thermal"),
41            Self::Timing => write!(f, "timing"),
42            Self::Scheduling => write!(f, "scheduling"),
43            Self::IO => write!(f, "io"),
44            Self::IPC => write!(f, "ipc"),
45            Self::Microarch => write!(f, "microarch"),
46            Self::GPU => write!(f, "gpu"),
47            Self::Network => write!(f, "network"),
48            Self::System => write!(f, "system"),
49            Self::Signal => write!(f, "signal"),
50            Self::Sensor => write!(f, "sensor"),
51            Self::Quantum => write!(f, "quantum"),
52        }
53    }
54}
55
56/// Target platform for an entropy source.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
58pub enum Platform {
59    /// Works on any platform.
60    Any,
61    /// Requires macOS.
62    MacOS,
63    /// Requires Linux.
64    Linux,
65}
66
67impl std::fmt::Display for Platform {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        match self {
70            Self::Any => write!(f, "any"),
71            Self::MacOS => write!(f, "macos"),
72            Self::Linux => write!(f, "linux"),
73        }
74    }
75}
76
77/// Hardware/software requirement for an entropy source.
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
79pub enum Requirement {
80    /// GPU compute (Metal framework).
81    Metal,
82    /// CoreAudio (AudioUnit/AudioObject).
83    AudioUnit,
84    /// WiFi hardware.
85    Wifi,
86    /// USB subsystem.
87    Usb,
88    /// Camera hardware.
89    Camera,
90    /// Apple Silicon specific features (AMX, etc.).
91    AppleSilicon,
92    /// Bluetooth hardware.
93    Bluetooth,
94    /// IOKit framework.
95    IOKit,
96    /// IOSurface framework.
97    IOSurface,
98    /// Security framework (Keychain).
99    SecurityFramework,
100    /// Raw block device access (/dev/rdiskN, /dev/nvmeXnY).
101    RawBlockDevice,
102    /// QCicada QRNG hardware (USB serial).
103    QCicada,
104}
105
106impl std::fmt::Display for Requirement {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        match self {
109            Self::Metal => write!(f, "metal"),
110            Self::AudioUnit => write!(f, "audio_unit"),
111            Self::Wifi => write!(f, "wifi"),
112            Self::Usb => write!(f, "usb"),
113            Self::Camera => write!(f, "camera"),
114            Self::AppleSilicon => write!(f, "apple_silicon"),
115            Self::Bluetooth => write!(f, "bluetooth"),
116            Self::IOKit => write!(f, "iokit"),
117            Self::IOSurface => write!(f, "iosurface"),
118            Self::SecurityFramework => write!(f, "security_framework"),
119            Self::RawBlockDevice => write!(f, "raw_block_device"),
120            Self::QCicada => write!(f, "qcicada"),
121        }
122    }
123}
124
125impl Requirement {
126    /// Emoji icon for hardware requirements.
127    pub fn icon(&self) -> &'static str {
128        match self {
129            Self::QCicada => "🔮",
130            Self::Camera => "📷",
131            Self::AudioUnit => "🎤",
132            Self::Metal => "🎮",
133            Self::Wifi => "📶",
134            Self::Bluetooth => "📡",
135            Self::Usb => "🔌",
136            Self::RawBlockDevice => "💾",
137            _ => "", // IOKit, IOSurface, SecurityFramework, AppleSilicon — no icon
138        }
139    }
140
141    /// Human-readable hardware label.
142    pub fn label(&self) -> &'static str {
143        match self {
144            Self::QCicada => "QCicada QRNG (USB)",
145            Self::Camera => "Camera",
146            Self::AudioUnit => "Microphone (CoreAudio)",
147            Self::Metal => "GPU (Metal)",
148            Self::Wifi => "WiFi adapter",
149            Self::Bluetooth => "Bluetooth",
150            Self::Usb => "USB subsystem",
151            Self::RawBlockDevice => "Raw block device",
152            Self::AppleSilicon => "Apple Silicon",
153            Self::IOKit => "IOKit",
154            Self::IOSurface => "IOSurface",
155            Self::SecurityFramework => "Security Framework",
156        }
157    }
158
159    /// Parse a requirement from its `Display` name (inverse of `to_string()`).
160    pub fn from_display_name(name: &str) -> Option<Self> {
161        match name {
162            "metal" => Some(Self::Metal),
163            "audio_unit" => Some(Self::AudioUnit),
164            "wifi" => Some(Self::Wifi),
165            "usb" => Some(Self::Usb),
166            "camera" => Some(Self::Camera),
167            "apple_silicon" => Some(Self::AppleSilicon),
168            "bluetooth" => Some(Self::Bluetooth),
169            "iokit" => Some(Self::IOKit),
170            "iosurface" => Some(Self::IOSurface),
171            "security_framework" => Some(Self::SecurityFramework),
172            "raw_block_device" => Some(Self::RawBlockDevice),
173            "qcicada" => Some(Self::QCicada),
174            _ => None,
175        }
176    }
177
178    /// Look up the icon for a requirement by its `Display` name.
179    ///
180    /// This is the canonical mapping used by CLI/TUI code that only has the
181    /// serialised string form (e.g. from [`SourceInfoSnapshot`]).
182    pub fn icon_for_display_name(name: &str) -> &'static str {
183        Self::from_display_name(name).map_or("", |r| r.icon())
184    }
185
186    /// Look up the label for a requirement by its `Display` name.
187    ///
188    /// Returns the human-readable label if the name is recognised, or a
189    /// generic `"Unknown"` for unrecognised names.
190    pub fn label_for_display_name(name: &str) -> &'static str {
191        Self::from_display_name(name).map_or("Unknown", |r| r.label())
192    }
193}
194
195/// Returns the first non-empty icon from a requirements list.
196pub fn best_icon(requirements: &[Requirement]) -> &'static str {
197    requirements
198        .iter()
199        .map(Requirement::icon)
200        .find(|icon| !icon.is_empty())
201        .unwrap_or("")
202}
203
204/// Returns the first non-empty icon from a list of requirement display names.
205pub fn best_icon_from_names(names: &[String]) -> &'static str {
206    names
207        .iter()
208        .map(|n| Requirement::icon_for_display_name(n))
209        .find(|icon| !icon.is_empty())
210        .unwrap_or("")
211}
212
213/// Metadata about an entropy source.
214///
215/// Each source declares its name, a human-readable description, a physics
216/// explanation of how it harvests entropy, its category, platform requirements,
217/// and an estimated entropy rate in bits per sample.
218#[derive(Debug, Clone)]
219pub struct SourceInfo {
220    /// Unique identifier (e.g. `"clock_jitter"`).
221    pub name: &'static str,
222    /// One-line human-readable description.
223    pub description: &'static str,
224    /// Physics explanation of the entropy mechanism.
225    pub physics: &'static str,
226    /// Source category for classification.
227    pub category: SourceCategory,
228    /// Target platform.
229    pub platform: Platform,
230    /// Hardware/software requirements beyond the platform.
231    pub requirements: &'static [Requirement],
232    /// Estimated entropy rate in bits per sample.
233    pub entropy_rate_estimate: f64,
234    /// Whether this is a composite source (combines multiple standalone sources).
235    ///
236    /// Composite sources don't measure a single independent entropy domain.
237    /// They combine or interleave other sources. The CLI displays them
238    /// separately from standalone sources.
239    pub composite: bool,
240    /// Whether this source collects in <2 seconds and is safe for real-time use.
241    pub is_fast: bool,
242}
243
244/// Trait that every entropy source must implement.
245pub trait EntropySource: Send + Sync {
246    /// Source metadata.
247    fn info(&self) -> &SourceInfo;
248
249    /// Check if this source can operate on the current machine.
250    fn is_available(&self) -> bool;
251
252    /// Collect raw entropy samples. Returns a `Vec<u8>` of up to `n_samples` bytes.
253    fn collect(&self, n_samples: usize) -> Vec<u8>;
254
255    /// Convenience: name from info.
256    fn name(&self) -> &'static str {
257        self.info().name
258    }
259
260    /// Optional runtime configuration. Returns Err if unsupported.
261    fn set_config(&self, _key: &str, _value: &str) -> Result<(), String> {
262        Err("source does not support runtime configuration".into())
263    }
264
265    /// List configurable keys and their current values.
266    fn config_options(&self) -> Vec<(&'static str, String)> {
267        vec![]
268    }
269}
270
271/// Runtime state for a registered source in the pool.
272pub struct SourceState {
273    pub source: std::sync::Arc<dyn EntropySource>,
274    pub total_bytes: u64,
275    pub failures: u64,
276    pub last_entropy: f64,
277    pub last_min_entropy: f64,
278    pub last_autocorrelation: f64,
279    pub last_collect_time: Duration,
280    pub healthy: bool,
281}
282
283impl SourceState {
284    pub fn new(source: Box<dyn EntropySource>) -> Self {
285        Self {
286            source: std::sync::Arc::from(source),
287            total_bytes: 0,
288            failures: 0,
289            last_entropy: 0.0,
290            last_min_entropy: 0.0,
291            last_autocorrelation: 0.0,
292            last_collect_time: Duration::ZERO,
293            healthy: true,
294        }
295    }
296}