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}