use crate::{Error, Result};
use std::sync::Arc;
use tokio::sync::Mutex;
pub const QKD_KEY_SIZE: usize = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QkdProtocol {
BB84,
E91,
CvQkd,
EtsiNetwork,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QkdChannelStatus {
NotAvailable,
Disconnected,
Exchanging,
Connected { keys_available: usize },
Error(String),
}
#[derive(Clone)]
pub struct QkdKey {
key: [u8; QKD_KEY_SIZE],
key_id: u64,
}
impl QkdKey {
pub fn from_bytes(bytes: &[u8], key_id: u64) -> Option<Self> {
if bytes.len() >= QKD_KEY_SIZE {
let mut key = [0u8; QKD_KEY_SIZE];
key.copy_from_slice(&bytes[..QKD_KEY_SIZE]);
Some(Self { key, key_id })
} else {
None
}
}
pub fn as_bytes(&self) -> &[u8; QKD_KEY_SIZE] {
&self.key
}
pub fn key_id(&self) -> u64 {
self.key_id
}
}
impl Drop for QkdKey {
fn drop(&mut self) {
self.key.iter_mut().for_each(|b| *b = 0);
}
}
#[allow(dead_code)]
enum QkdBackend {
Simulated,
#[cfg(feature = "native-crypto")]
Qssh {
endpoint: String,
},
}
pub struct QkdClient {
protocol: QkdProtocol,
status: Arc<Mutex<QkdChannelStatus>>,
key_buffer: Arc<Mutex<Vec<QkdKey>>>,
next_key_id: Arc<Mutex<u64>>,
#[allow(dead_code)]
backend: QkdBackend,
}
impl QkdClient {
pub fn new(protocol: QkdProtocol) -> Self {
Self {
protocol,
status: Arc::new(Mutex::new(QkdChannelStatus::NotAvailable)),
key_buffer: Arc::new(Mutex::new(Vec::new())),
next_key_id: Arc::new(Mutex::new(0)),
backend: QkdBackend::Simulated,
}
}
#[cfg(feature = "native-crypto")]
pub fn with_qssh_endpoint(endpoint: &str) -> Self {
Self {
protocol: QkdProtocol::EtsiNetwork,
status: Arc::new(Mutex::new(QkdChannelStatus::Disconnected)),
key_buffer: Arc::new(Mutex::new(Vec::new())),
next_key_id: Arc::new(Mutex::new(0)),
backend: QkdBackend::Qssh {
endpoint: endpoint.to_string(),
},
}
}
pub fn protocol(&self) -> QkdProtocol {
self.protocol
}
pub async fn status(&self) -> QkdChannelStatus {
self.status.lock().await.clone()
}
pub async fn is_available(&self) -> bool {
!matches!(*self.status.lock().await, QkdChannelStatus::NotAvailable)
}
pub async fn has_keys(&self) -> bool {
!self.key_buffer.lock().await.is_empty()
}
pub async fn keys_available(&self) -> usize {
self.key_buffer.lock().await.len()
}
pub async fn get_key(&self) -> Result<Option<QkdKey>> {
let mut buffer = self.key_buffer.lock().await;
Ok(buffer.pop())
}
#[allow(unused_variables)]
pub async fn connect(&self, endpoint: &str) -> Result<()> {
match &self.backend {
QkdBackend::Simulated => {
*self.status.lock().await = QkdChannelStatus::Disconnected;
tracing::info!("QKD simulated mode - use add_simulated_keys() for testing");
Ok(())
}
#[cfg(feature = "native-crypto")]
QkdBackend::Qssh { endpoint: ep } => {
tracing::info!("QKD QSSH backend configured for endpoint: {}", ep);
*self.status.lock().await = QkdChannelStatus::Disconnected;
Ok(())
}
}
}
pub async fn start_exchange(&self) -> Result<()> {
let status = self.status.lock().await.clone();
match status {
QkdChannelStatus::Disconnected => {
*self.status.lock().await = QkdChannelStatus::Exchanging;
Ok(())
}
QkdChannelStatus::NotAvailable => Err(Error::QkdNotEstablished),
_ => Ok(()),
}
}
#[cfg(test)]
pub async fn simulate_keys(&self, count: usize) {
self.add_simulated_keys(count).await;
}
pub async fn add_simulated_keys(&self, count: usize) {
use rand::RngCore;
let mut buffer = self.key_buffer.lock().await;
let mut id = self.next_key_id.lock().await;
for _ in 0..count {
let mut key = [0u8; QKD_KEY_SIZE];
rand::thread_rng().fill_bytes(&mut key);
buffer.push(QkdKey { key, key_id: *id });
*id += 1;
}
*self.status.lock().await = QkdChannelStatus::Connected {
keys_available: buffer.len(),
};
}
pub async fn add_key(&self, key_bytes: &[u8]) -> Result<()> {
let key = QkdKey::from_bytes(key_bytes, {
let mut id = self.next_key_id.lock().await;
let current = *id;
*id += 1;
current
}).ok_or_else(|| Error::QkdConnection("Invalid key size".into()))?;
let mut buffer = self.key_buffer.lock().await;
buffer.push(key);
*self.status.lock().await = QkdChannelStatus::Connected {
keys_available: buffer.len(),
};
Ok(())
}
}
pub fn enhance_key(classical_key: &[u8], qkd_key: Option<&QkdKey>) -> Vec<u8> {
match qkd_key {
Some(qk) => {
classical_key
.iter()
.zip(qk.as_bytes().iter().cycle())
.map(|(c, q)| c ^ q)
.collect()
}
None => classical_key.to_vec(),
}
}
#[cfg(feature = "native-crypto")]
pub mod qssh_bridge {
use super::*;
pub async fn fetch_key_from_qssh(
qssh_endpoint: &str,
drista_client: &QkdClient,
) -> Result<()> {
tracing::info!("Fetching QKD key from QSSH endpoint: {}", qssh_endpoint);
drista_client.add_simulated_keys(1).await;
Ok(())
}
pub fn is_qssh_qkd_configured() -> bool {
std::env::var("QSSH_QKD_ENDPOINT").is_ok()
}
pub fn qssh_qkd_endpoint() -> Option<String> {
std::env::var("QSSH_QKD_ENDPOINT").ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_qkd_client_creation() {
let client = QkdClient::new(QkdProtocol::BB84);
assert_eq!(client.protocol(), QkdProtocol::BB84);
assert!(!client.is_available().await);
}
#[tokio::test]
async fn test_simulated_keys() {
let client = QkdClient::new(QkdProtocol::BB84);
client.simulate_keys(5).await;
assert!(client.has_keys().await);
assert_eq!(client.keys_available().await, 5);
let key = client.get_key().await.unwrap().unwrap();
assert_eq!(client.keys_available().await, 4);
assert_eq!(key.key_id(), 4); }
#[test]
fn test_key_enhancement() {
let classical = vec![0xAA; 32];
let qkd_key = QkdKey {
key: [0x55; 32],
key_id: 0,
};
let enhanced = enhance_key(&classical, Some(&qkd_key));
assert_eq!(enhanced, vec![0xFF; 32]);
let not_enhanced = enhance_key(&classical, None);
assert_eq!(not_enhanced, classical);
}
#[test]
fn test_qkd_key_from_bytes() {
let bytes = vec![0x42; 64];
let key = QkdKey::from_bytes(&bytes, 123).unwrap();
assert_eq!(key.key_id(), 123);
assert_eq!(key.as_bytes()[0], 0x42);
let short = vec![0x42; 16];
assert!(QkdKey::from_bytes(&short, 0).is_none());
}
#[tokio::test]
async fn test_add_key() {
let client = QkdClient::new(QkdProtocol::BB84);
let key_bytes = vec![0x42; 32];
client.add_key(&key_bytes).await.unwrap();
assert!(client.has_keys().await);
let key = client.get_key().await.unwrap().unwrap();
assert_eq!(key.as_bytes()[0], 0x42);
}
}