quantacore/device.rs
1//! QUAC 100 device management.
2//!
3//! This module provides the `Device` struct which represents an open
4//! connection to a QUAC 100 hardware device.
5
6use crate::error::{check_error, Result};
7use crate::ffi;
8use crate::hash::Hash;
9use crate::kem::Kem;
10use crate::keys::Keys;
11use crate::random::Random;
12use crate::sign::Sign;
13
14use std::ffi::CStr;
15use std::sync::Arc;
16
17/// Device information.
18#[derive(Debug, Clone)]
19pub struct DeviceInfo {
20 /// Device index
21 pub index: u32,
22 /// Device model name
23 pub model: String,
24 /// Serial number
25 pub serial_number: String,
26 /// Firmware version
27 pub firmware_version: String,
28 /// Driver version
29 pub driver_version: String,
30 /// Number of key slots
31 pub key_slots: u32,
32 /// Maximum key size
33 pub max_key_size: u32,
34 /// Feature flags
35 pub features: u32,
36}
37
38impl DeviceInfo {
39 /// Create from FFI structure.
40 pub(crate) fn from_ffi(info: &ffi::quac_device_info_t) -> Self {
41 Self {
42 index: info.index,
43 model: c_array_to_string(&info.model),
44 serial_number: c_array_to_string(&info.serial_number),
45 firmware_version: c_array_to_string(&info.firmware_version),
46 driver_version: c_array_to_string(&info.driver_version),
47 key_slots: info.key_slots,
48 max_key_size: info.max_key_size,
49 features: info.features,
50 }
51 }
52}
53
54/// Device status.
55#[derive(Debug, Clone)]
56pub struct DeviceStatus {
57 /// Temperature in Celsius
58 pub temperature: i32,
59 /// Entropy pool level (0-100)
60 pub entropy_level: u32,
61 /// Total operations performed
62 pub operation_count: u64,
63 /// Total errors encountered
64 pub error_count: u64,
65 /// Uptime in seconds
66 pub uptime_seconds: u64,
67 /// Status flags
68 pub flags: u32,
69}
70
71impl DeviceStatus {
72 /// Check if the device is healthy.
73 pub fn is_healthy(&self) -> bool {
74 // Check temperature range (-10 to 85°C for typical FPGA)
75 let temp_ok = (-10..=85).contains(&self.temperature);
76 // Check entropy level
77 let entropy_ok = self.entropy_level >= 10;
78 // Check no critical flags
79 let flags_ok = (self.flags & 0x80000000) == 0; // MSB = critical error
80
81 temp_ok && entropy_ok && flags_ok
82 }
83
84 pub(crate) fn from_ffi(status: &ffi::quac_device_status_t) -> Self {
85 Self {
86 temperature: status.temperature,
87 entropy_level: status.entropy_level,
88 operation_count: status.operation_count,
89 error_count: status.error_count,
90 uptime_seconds: status.uptime_seconds,
91 flags: status.flags,
92 }
93 }
94}
95
96/// Inner device state (shared via Arc).
97struct DeviceInner {
98 handle: ffi::quac_device_t,
99 index: u32,
100}
101
102impl Drop for DeviceInner {
103 fn drop(&mut self) {
104 unsafe {
105 ffi::quac_close_device(self.handle);
106 }
107 }
108}
109
110// Safety: The device handle is thread-safe per the native library spec
111unsafe impl Send for DeviceInner {}
112unsafe impl Sync for DeviceInner {}
113
114/// Handle to an open QUAC 100 device.
115///
116/// This struct provides access to all device functionality including
117/// KEM, signatures, hashing, random number generation, and key storage.
118///
119/// The device is automatically closed when this struct is dropped.
120///
121/// # Thread Safety
122///
123/// `Device` is `Send` and `Sync`, allowing it to be shared across threads.
124/// The underlying hardware operations are serialized by the device.
125///
126/// # Example
127///
128/// ```no_run
129/// use quantacore::{initialize, open_first_device};
130///
131/// initialize().unwrap();
132/// let device = open_first_device().unwrap();
133///
134/// let info = device.get_info().unwrap();
135/// println!("Connected to: {}", info.model);
136///
137/// // Access subsystems
138/// let kem = device.kem();
139/// let sign = device.sign();
140/// let hash = device.hash();
141/// let random = device.random();
142/// ```
143#[derive(Clone)]
144pub struct Device {
145 inner: Arc<DeviceInner>,
146}
147
148impl Device {
149 /// Create a Device from a raw FFI handle.
150 pub(crate) fn from_raw(handle: ffi::quac_device_t, index: u32) -> Self {
151 Self {
152 inner: Arc::new(DeviceInner { handle, index }),
153 }
154 }
155
156 /// Get the raw FFI handle.
157 pub(crate) fn handle(&self) -> ffi::quac_device_t {
158 self.inner.handle
159 }
160
161 /// Get device index.
162 pub fn index(&self) -> u32 {
163 self.inner.index
164 }
165
166 /// Get device information.
167 ///
168 /// # Example
169 ///
170 /// ```no_run
171 /// # use quantacore::{initialize, open_first_device};
172 /// # initialize().unwrap();
173 /// let device = open_first_device().unwrap();
174 /// let info = device.get_info().unwrap();
175 /// println!("Model: {}", info.model);
176 /// println!("Serial: {}", info.serial_number);
177 /// println!("Firmware: {}", info.firmware_version);
178 /// ```
179 pub fn get_info(&self) -> Result<DeviceInfo> {
180 let mut info = ffi::quac_device_info_t::default();
181 let result = unsafe { ffi::quac_get_device_info(self.inner.index, &mut info) };
182 check_error(result)?;
183 Ok(DeviceInfo::from_ffi(&info))
184 }
185
186 /// Get device status.
187 ///
188 /// # Example
189 ///
190 /// ```no_run
191 /// # use quantacore::{initialize, open_first_device};
192 /// # initialize().unwrap();
193 /// let device = open_first_device().unwrap();
194 /// let status = device.get_status().unwrap();
195 /// println!("Temperature: {}°C", status.temperature);
196 /// println!("Entropy: {}%", status.entropy_level);
197 /// println!("Healthy: {}", status.is_healthy());
198 /// ```
199 pub fn get_status(&self) -> Result<DeviceStatus> {
200 let mut status = ffi::quac_device_status_t::default();
201 let result = unsafe { ffi::quac_device_get_status(self.inner.handle, &mut status) };
202 check_error(result)?;
203 Ok(DeviceStatus::from_ffi(&status))
204 }
205
206 /// Run device self-test.
207 ///
208 /// This performs a comprehensive self-test of the device hardware
209 /// and cryptographic implementations.
210 ///
211 /// # Example
212 ///
213 /// ```no_run
214 /// # use quantacore::{initialize, open_first_device};
215 /// # initialize().unwrap();
216 /// let device = open_first_device().unwrap();
217 /// device.self_test().expect("Self-test failed");
218 /// println!("Device self-test passed");
219 /// ```
220 pub fn self_test(&self) -> Result<()> {
221 let result = unsafe { ffi::quac_device_self_test(self.inner.handle) };
222 check_error(result)
223 }
224
225 /// Reset the device.
226 ///
227 /// This performs a soft reset of the device. All ongoing operations
228 /// will be cancelled.
229 pub fn reset(&self) -> Result<()> {
230 let result = unsafe { ffi::quac_device_reset(self.inner.handle) };
231 check_error(result)
232 }
233
234 /// Get the KEM (Key Encapsulation Mechanism) subsystem.
235 ///
236 /// # Example
237 ///
238 /// ```no_run
239 /// # use quantacore::{initialize, open_first_device, KemAlgorithm};
240 /// # initialize().unwrap();
241 /// let device = open_first_device().unwrap();
242 /// let kem = device.kem();
243 /// let keypair = kem.generate_keypair(KemAlgorithm::MlKem768).unwrap();
244 /// ```
245 pub fn kem(&self) -> Kem {
246 Kem::new(self.clone())
247 }
248
249 /// Get the signature subsystem.
250 ///
251 /// # Example
252 ///
253 /// ```no_run
254 /// # use quantacore::{initialize, open_first_device, SignAlgorithm};
255 /// # initialize().unwrap();
256 /// let device = open_first_device().unwrap();
257 /// let sign = device.sign();
258 /// let keypair = sign.generate_keypair(SignAlgorithm::MlDsa65).unwrap();
259 /// ```
260 pub fn sign(&self) -> Sign {
261 Sign::new(self.clone())
262 }
263
264 /// Get the hash subsystem.
265 ///
266 /// # Example
267 ///
268 /// ```no_run
269 /// # use quantacore::{initialize, open_first_device};
270 /// # initialize().unwrap();
271 /// let device = open_first_device().unwrap();
272 /// let hash = device.hash();
273 /// let digest = hash.sha256(b"Hello, World!").unwrap();
274 /// ```
275 pub fn hash(&self) -> Hash {
276 Hash::new(self.clone())
277 }
278
279 /// Get the random number generator subsystem.
280 ///
281 /// # Example
282 ///
283 /// ```no_run
284 /// # use quantacore::{initialize, open_first_device};
285 /// # initialize().unwrap();
286 /// let device = open_first_device().unwrap();
287 /// let random = device.random();
288 /// let bytes = random.bytes(32).unwrap();
289 /// ```
290 pub fn random(&self) -> Random {
291 Random::new(self.clone())
292 }
293
294 /// Get the key storage (HSM) subsystem.
295 ///
296 /// # Example
297 ///
298 /// ```no_run
299 /// # use quantacore::{initialize, open_first_device};
300 /// # initialize().unwrap();
301 /// let device = open_first_device().unwrap();
302 /// let keys = device.keys();
303 /// let slots = keys.list().unwrap();
304 /// ```
305 pub fn keys(&self) -> Keys {
306 Keys::new(self.clone())
307 }
308
309 /// Close the device explicitly.
310 ///
311 /// This is optional; the device will be closed automatically when dropped.
312 pub fn close(self) {
313 drop(self);
314 }
315}
316
317impl std::fmt::Debug for Device {
318 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
319 f.debug_struct("Device")
320 .field("index", &self.inner.index)
321 .finish()
322 }
323}
324
325/// Convert a C char array to a Rust String.
326fn c_array_to_string(arr: &[std::os::raw::c_char]) -> String {
327 unsafe {
328 CStr::from_ptr(arr.as_ptr())
329 .to_string_lossy()
330 .into_owned()
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 #[test]
339 fn test_device_status_is_healthy() {
340 let status = DeviceStatus {
341 temperature: 45,
342 entropy_level: 80,
343 operation_count: 1000,
344 error_count: 0,
345 uptime_seconds: 3600,
346 flags: 0,
347 };
348 assert!(status.is_healthy());
349
350 let hot_status = DeviceStatus {
351 temperature: 100,
352 ..status.clone()
353 };
354 assert!(!hot_status.is_healthy());
355
356 let low_entropy = DeviceStatus {
357 entropy_level: 5,
358 ..status
359 };
360 assert!(!low_entropy.is_healthy());
361 }
362}