use crate::guardian::error::{GuardianError, Result};
use crate::keystore::RedbKeystore;
use crate::log::identity_provider::Keystore;
use crate::p2p::network::config::ClientConfig;
use chrono::{DateTime, Utc};
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use iroh::EndpointId as NodeId;
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use tokio::sync::{Mutex, RwLock};
use tracing::{debug, info, warn};
use uuid::Uuid;
const SYNC_PROTOCOL_VERSION: u32 = 1;
const MAX_MESSAGE_AGE: Duration = Duration::from_secs(300);
#[allow(dead_code)]
const MAX_SYNC_RETRIES: u8 = 3;
const MAX_SYNC_QUEUE_SIZE: usize = 1000;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum KeySyncStatus {
Synchronized,
Synchronizing,
Pending,
Failed(String),
Conflict(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SyncOperation {
Create,
Update,
Delete,
MetadataSync,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyMetadata {
pub key_id: String,
pub version: u64,
pub last_modified: DateTime<Utc>,
pub creator: NodeId,
pub signature: Vec<u8>,
pub crypto_algorithm: String,
pub public_key_hash: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncMessage {
pub message_id: Uuid,
pub protocol_version: u32,
pub timestamp: SystemTime,
pub sender: NodeId,
pub operation: SyncOperation,
pub metadata: KeyMetadata,
pub key_data: Option<Vec<u8>>,
pub message_signature: Vec<u8>,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
struct SyncQueueEntry {
#[allow(dead_code)]
message: SyncMessage,
#[allow(dead_code)]
retry_count: u8,
#[allow(dead_code)]
next_retry: SystemTime,
#[allow(dead_code)]
target_peers: Vec<NodeId>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct SyncStatistics {
pub messages_synced: u64,
pub pending_messages: u64,
pub conflicts_detected: u64,
pub conflicts_resolved: u64,
pub success_rate: f64,
pub avg_sync_latency_ms: f64,
pub active_peers: u32,
}
pub struct KeySynchronizer {
#[allow(dead_code)]
client_config: ClientConfig,
local_keystore: Arc<RedbKeystore>,
node_signing_key: SigningKey,
node_id: NodeId,
synchronized_keys: Arc<RwLock<HashMap<String, KeyMetadata>>>,
sync_status: Arc<RwLock<HashMap<String, KeySyncStatus>>>,
sync_queue: Arc<Mutex<VecDeque<SyncQueueEntry>>>,
message_cache: Arc<RwLock<HashMap<Uuid, SystemTime>>>,
statistics: Arc<RwLock<SyncStatistics>>,
trusted_peers: Arc<RwLock<HashMap<NodeId, VerifyingKey>>>,
}
impl KeySynchronizer {
pub async fn new(client_config: &ClientConfig) -> Result<Self> {
let keystore_path = client_config
.data_store_path
.as_ref()
.map(|p| p.join("keystore"))
.unwrap_or_else(|| std::env::temp_dir().join("guardian_keystore"));
let local_keystore = Arc::new(RedbKeystore::new(Some(keystore_path))?);
let node_signing_key = Self::load_or_generate_keypair(&local_keystore).await?;
let node_id = NodeId::from_bytes(node_signing_key.verifying_key().as_bytes())
.map_err(|e| GuardianError::Other(format!("Invalid public key: {}", e)))?;
info!("Initializing key synchronizer for NodeID: {}", node_id);
Ok(Self {
client_config: client_config.clone(),
local_keystore,
node_signing_key,
node_id,
synchronized_keys: Arc::new(RwLock::new(HashMap::new())),
sync_status: Arc::new(RwLock::new(HashMap::new())),
sync_queue: Arc::new(Mutex::new(VecDeque::new())),
message_cache: Arc::new(RwLock::new(HashMap::new())),
statistics: Arc::new(RwLock::new(SyncStatistics::default())),
trusted_peers: Arc::new(RwLock::new(HashMap::new())),
})
}
pub fn node_id(&self) -> NodeId {
self.node_id
}
pub fn signing_key(&self) -> &SigningKey {
&self.node_signing_key
}
async fn load_or_generate_keypair(keystore: &RedbKeystore) -> Result<SigningKey> {
const MAIN_KEYPAIR_KEY: &str = "main_node_keypair";
if let Some(data) = keystore.get(MAIN_KEYPAIR_KEY).await?
&& data.len() == 32
{
debug!("Loading existing main keypair");
return SigningKey::try_from(&data[..32])
.map_err(|e| GuardianError::Other(format!("Error loading keypair: {}", e)));
}
let mut secret_bytes = [0u8; 32];
rand::rng().fill_bytes(&mut secret_bytes);
let signing_key = SigningKey::from_bytes(&secret_bytes);
keystore
.put(MAIN_KEYPAIR_KEY, signing_key.as_bytes())
.await?;
info!("New main keypair generated and saved");
Ok(signing_key)
}
pub async fn add_trusted_peer(&self, node_id: NodeId, public_key: VerifyingKey) -> Result<()> {
let mut trusted = self.trusted_peers.write().await;
trusted.insert(node_id, public_key);
info!("Trusted peer added: {}", node_id);
Ok(())
}
pub async fn remove_trusted_peer(&self, node_id: &NodeId) -> Result<bool> {
let mut trusted = self.trusted_peers.write().await;
let removed = trusted.remove(node_id).is_some();
if removed {
info!("Peer removed from the trusted list: {}", node_id);
}
Ok(removed)
}
pub async fn sync_key(&self, key_id: &str, operation: SyncOperation) -> Result<()> {
debug!(
"Starting synchronization of key: {} (operation: {:?})",
key_id, operation
);
let metadata = self.get_key_metadata(key_id).await?;
let message = self.create_sync_message(operation, metadata, None).await?;
self.enqueue_sync_message(message).await?;
self.update_sync_status(key_id, KeySyncStatus::Synchronizing)
.await;
Ok(())
}
pub async fn handle_sync_message(&self, message: SyncMessage) -> Result<()> {
if self.is_message_too_old(&message)? {
warn!(
"Synchronization message rejected (too old): {:?}",
message.message_id
);
return Err(GuardianError::Other("Message too old".to_string()));
}
if self.is_message_duplicate(&message).await? {
debug!("Duplicate message ignored: {:?}", message.message_id);
return Ok(());
}
self.verify_message_signature(&message).await?;
match message.operation {
SyncOperation::Create => self.handle_key_create(&message).await?,
SyncOperation::Update => self.handle_key_update(&message).await?,
SyncOperation::Delete => self.handle_key_delete(&message).await?,
SyncOperation::MetadataSync => self.handle_metadata_sync(&message).await?,
}
self.cache_processed_message(&message).await;
self.update_statistics().await;
Ok(())
}
async fn get_key_metadata(&self, key_id: &str) -> Result<KeyMetadata> {
let synchronized_keys = self.synchronized_keys.read().await;
if let Some(metadata) = synchronized_keys.get(key_id) {
return Ok(metadata.clone());
}
let key_data = self
.local_keystore
.get(key_id)
.await?
.ok_or_else(|| GuardianError::Other(format!("Key not found: {}", key_id)))?;
let public_key_hash = blake3::hash(&key_data).as_bytes().to_vec();
let metadata = KeyMetadata {
key_id: key_id.to_string(),
version: 1,
last_modified: Utc::now(),
creator: self.node_id,
signature: Vec::new(), crypto_algorithm: "Ed25519".to_string(),
public_key_hash,
};
Ok(metadata)
}
async fn create_sync_message(
&self,
operation: SyncOperation,
metadata: KeyMetadata,
key_data: Option<Vec<u8>>,
) -> Result<SyncMessage> {
let message = SyncMessage {
message_id: Uuid::new_v4(),
protocol_version: SYNC_PROTOCOL_VERSION,
timestamp: SystemTime::now(),
sender: self.node_id,
operation,
metadata,
key_data,
message_signature: Vec::new(), };
let signed_message = self.sign_sync_message(message).await?;
Ok(signed_message)
}
async fn sign_sync_message(&self, mut message: SyncMessage) -> Result<SyncMessage> {
let mut message_copy = message.clone();
message_copy.message_signature.clear();
let message_bytes = postcard::to_allocvec(&message_copy)
.map_err(|e| GuardianError::Other(format!("Error serializing message: {}", e)))?;
let signature = self
.node_signing_key
.sign(&message_bytes)
.to_bytes()
.to_vec();
message.message_signature = signature;
Ok(message)
}
async fn verify_message_signature(&self, message: &SyncMessage) -> Result<()> {
let trusted_peers = self.trusted_peers.read().await;
let verifying_key = trusted_peers
.get(&message.sender)
.ok_or_else(|| GuardianError::Other(format!("Untrusted peer: {}", message.sender)))?;
let mut message_copy = message.clone();
message_copy.message_signature.clear();
let message_bytes = postcard::to_allocvec(&message_copy)
.map_err(|e| GuardianError::Other(format!("Error serializing message: {}", e)))?;
let signature = Signature::from_slice(&message.message_signature)
.map_err(|e| GuardianError::Other(format!("Invalid signature: {}", e)))?;
verifying_key
.verify(&message_bytes, &signature)
.map_err(|e| GuardianError::Other(format!("Signature verification failed: {}", e)))?;
Ok(())
}
fn is_message_too_old(&self, message: &SyncMessage) -> Result<bool> {
let now = SystemTime::now();
let age = now
.duration_since(message.timestamp)
.map_err(|_| GuardianError::Other("Invalid timestamp".to_string()))?;
Ok(age > MAX_MESSAGE_AGE)
}
async fn is_message_duplicate(&self, message: &SyncMessage) -> Result<bool> {
let cache = self.message_cache.read().await;
Ok(cache.contains_key(&message.message_id))
}
async fn enqueue_sync_message(&self, message: SyncMessage) -> Result<()> {
let mut queue = self.sync_queue.lock().await;
if queue.len() >= MAX_SYNC_QUEUE_SIZE {
queue.pop_front();
warn!("Synchronization queue full, removing the oldest message");
}
let entry = SyncQueueEntry {
message,
retry_count: 0,
next_retry: SystemTime::now(),
target_peers: Vec::new(), };
queue.push_back(entry);
debug!("Message added to the synchronization queue");
Ok(())
}
async fn handle_key_create(&self, message: &SyncMessage) -> Result<()> {
let key_id = &message.metadata.key_id;
if self.local_keystore.has(key_id).await? {
let local_metadata = self.get_key_metadata(key_id).await?;
if local_metadata.version >= message.metadata.version {
debug!(
"Key already exists with an equal or higher version: {}",
key_id
);
return Ok(());
}
}
if let Some(key_data) = &message.key_data {
self.local_keystore.put(key_id, key_data).await?;
}
let mut synchronized_keys = self.synchronized_keys.write().await;
synchronized_keys.insert(key_id.clone(), message.metadata.clone());
self.update_sync_status(key_id, KeySyncStatus::Synchronized)
.await;
info!("Key created via synchronization: {}", key_id);
Ok(())
}
async fn handle_key_update(&self, message: &SyncMessage) -> Result<()> {
let key_id = &message.metadata.key_id;
if !self.local_keystore.has(key_id).await? {
warn!("Attempt to update a non-existent key: {}", key_id);
return Err(GuardianError::Other(format!("Key not found: {}", key_id)));
}
let local_metadata = self.get_key_metadata(key_id).await?;
if local_metadata.version > message.metadata.version {
warn!("Version conflict detected for key: {}", key_id);
self.update_sync_status(
key_id,
KeySyncStatus::Conflict(format!(
"Local: v{}, Remote: v{}",
local_metadata.version, message.metadata.version
)),
)
.await;
return Ok(());
}
if let Some(key_data) = &message.key_data {
self.local_keystore.put(key_id, key_data).await?;
}
let mut synchronized_keys = self.synchronized_keys.write().await;
synchronized_keys.insert(key_id.clone(), message.metadata.clone());
self.update_sync_status(key_id, KeySyncStatus::Synchronized)
.await;
info!("Key updated via synchronization: {}", key_id);
Ok(())
}
async fn handle_key_delete(&self, message: &SyncMessage) -> Result<()> {
let key_id = &message.metadata.key_id;
self.local_keystore.delete(key_id).await?;
let mut synchronized_keys = self.synchronized_keys.write().await;
synchronized_keys.remove(key_id);
let mut sync_status = self.sync_status.write().await;
sync_status.remove(key_id);
info!("Key deleted via synchronization: {}", key_id);
Ok(())
}
async fn handle_metadata_sync(&self, message: &SyncMessage) -> Result<()> {
let key_id = &message.metadata.key_id;
let mut synchronized_keys = self.synchronized_keys.write().await;
synchronized_keys.insert(key_id.clone(), message.metadata.clone());
debug!("Metadata synchronized for key: {}", key_id);
Ok(())
}
async fn cache_processed_message(&self, message: &SyncMessage) {
let mut cache = self.message_cache.write().await;
cache.insert(message.message_id, SystemTime::now());
let cutoff = SystemTime::now() - MAX_MESSAGE_AGE;
cache.retain(|_, timestamp| *timestamp > cutoff);
}
async fn update_sync_status(&self, key_id: &str, status: KeySyncStatus) {
let mut sync_status = self.sync_status.write().await;
sync_status.insert(key_id.to_string(), status);
}
async fn update_statistics(&self) {
let mut stats = self.statistics.write().await;
stats.messages_synced += 1;
let queue = self.sync_queue.lock().await;
stats.pending_messages = queue.len() as u64;
let trusted_peers = self.trusted_peers.read().await;
stats.active_peers = trusted_peers.len() as u32;
let sync_status = self.sync_status.read().await;
let total_keys = sync_status.len() as u64;
let synchronized_keys = sync_status
.values()
.filter(|status| matches!(status, KeySyncStatus::Synchronized))
.count() as u64;
stats.success_rate = if total_keys > 0 {
(synchronized_keys as f64 / total_keys as f64) * 100.0
} else {
100.0
};
}
pub async fn get_statistics(&self) -> SyncStatistics {
self.statistics.read().await.clone()
}
pub async fn get_key_sync_status(&self, key_id: &str) -> Option<KeySyncStatus> {
let sync_status = self.sync_status.read().await;
sync_status.get(key_id).cloned()
}
pub async fn list_synchronized_keys(&self) -> Vec<String> {
let synchronized_keys = self.synchronized_keys.read().await;
synchronized_keys.keys().cloned().collect()
}
pub async fn list_trusted_peers(&self) -> Vec<NodeId> {
let trusted = self.trusted_peers.read().await;
trusted.keys().copied().collect()
}
pub async fn force_full_sync(&self) -> Result<()> {
info!("Starting forced full synchronization");
let keys = self.local_keystore.list_keys().await?;
for key_id in keys {
self.sync_key(&key_id, SyncOperation::MetadataSync).await?;
}
info!(
"Forced full synchronization started for {} keys",
self.synchronized_keys.read().await.len()
);
Ok(())
}
pub async fn export_sync_config(&self) -> Result<Vec<u8>> {
let sync_config = SyncExportConfig {
node_id: self.node_id,
trusted_peers: self.trusted_peers.read().await.clone(),
synchronized_keys: self.synchronized_keys.read().await.clone(),
statistics: self.statistics.read().await.clone(),
};
postcard::to_allocvec(&sync_config)
.map_err(|e| GuardianError::Other(format!("Error exporting configuration: {}", e)))
}
}
#[derive(Debug, Serialize, Deserialize)]
struct SyncExportConfig {
node_id: NodeId,
trusted_peers: HashMap<NodeId, VerifyingKey>,
synchronized_keys: HashMap<String, KeyMetadata>,
statistics: SyncStatistics,
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[tokio::test]
async fn test_key_synchronizer_creation() {
let temp_dir = TempDir::new().unwrap();
let client_config = ClientConfig {
data_store_path: Some(temp_dir.path().to_path_buf()),
..Default::default()
};
let synchronizer = KeySynchronizer::new(&client_config).await.unwrap();
assert!(!synchronizer.node_id().to_string().is_empty());
}
#[tokio::test]
async fn test_sync_message_creation_and_verification() {
let temp_dir = TempDir::new().unwrap();
let client_config = ClientConfig {
data_store_path: Some(temp_dir.path().to_path_buf()),
..Default::default()
};
let synchronizer = KeySynchronizer::new(&client_config).await.unwrap();
let metadata = KeyMetadata {
key_id: "test_key".to_string(),
version: 1,
last_modified: Utc::now(),
creator: synchronizer.node_id(),
signature: Vec::new(),
crypto_algorithm: "Ed25519".to_string(),
public_key_hash: vec![1, 2, 3, 4],
};
let message = synchronizer
.create_sync_message(SyncOperation::Create, metadata, Some(b"test_data".to_vec()))
.await
.unwrap();
assert_eq!(message.protocol_version, SYNC_PROTOCOL_VERSION);
assert_eq!(message.sender, synchronizer.node_id());
assert_eq!(message.operation, SyncOperation::Create);
assert!(!message.message_signature.is_empty());
}
fn config_at(temp: &TempDir) -> ClientConfig {
ClientConfig {
data_store_path: Some(temp.path().to_path_buf()),
..Default::default()
}
}
#[tokio::test]
async fn keypair_persists_across_reload() {
let temp_dir = TempDir::new().unwrap();
let config = config_at(&temp_dir);
let first = KeySynchronizer::new(&config).await.unwrap();
let id_first = first.node_id();
drop(first);
let second = KeySynchronizer::new(&config).await.unwrap();
assert_eq!(
id_first,
second.node_id(),
"the keypair should be persisted and reloaded"
);
}
#[tokio::test]
async fn distinct_data_paths_yield_distinct_keys() {
let a = TempDir::new().unwrap();
let b = TempDir::new().unwrap();
let sync_a = KeySynchronizer::new(&config_at(&a)).await.unwrap();
let sync_b = KeySynchronizer::new(&config_at(&b)).await.unwrap();
assert_ne!(sync_a.node_id(), sync_b.node_id());
}
#[tokio::test]
async fn add_list_remove_trusted_peer() {
let temp_dir = TempDir::new().unwrap();
let sync = KeySynchronizer::new(&config_at(&temp_dir)).await.unwrap();
let peer = iroh::SecretKey::generate().public();
let vkey = sync.signing_key().verifying_key();
assert!(sync.list_trusted_peers().await.is_empty());
sync.add_trusted_peer(peer, vkey).await.unwrap();
let peers = sync.list_trusted_peers().await;
assert_eq!(peers.len(), 1);
assert!(peers.contains(&peer));
assert!(sync.remove_trusted_peer(&peer).await.unwrap());
assert!(sync.list_trusted_peers().await.is_empty());
}
#[tokio::test]
async fn remove_absent_trusted_peer_returns_false() {
let temp_dir = TempDir::new().unwrap();
let sync = KeySynchronizer::new(&config_at(&temp_dir)).await.unwrap();
let absent = iroh::SecretKey::generate().public();
assert!(!sync.remove_trusted_peer(&absent).await.unwrap());
}
#[tokio::test]
async fn add_trusted_peer_is_idempotent_on_node_id() {
let temp_dir = TempDir::new().unwrap();
let sync = KeySynchronizer::new(&config_at(&temp_dir)).await.unwrap();
let peer = iroh::SecretKey::generate().public();
let vkey = sync.signing_key().verifying_key();
sync.add_trusted_peer(peer, vkey).await.unwrap();
sync.add_trusted_peer(peer, vkey).await.unwrap(); assert_eq!(sync.list_trusted_peers().await.len(), 1);
}
#[tokio::test]
async fn fresh_synchronizer_has_empty_state() {
let temp_dir = TempDir::new().unwrap();
let sync = KeySynchronizer::new(&config_at(&temp_dir)).await.unwrap();
assert!(sync.list_synchronized_keys().await.is_empty());
assert!(sync.list_trusted_peers().await.is_empty());
}
#[tokio::test]
async fn export_sync_config_produces_decodable_bytes() {
let temp_dir = TempDir::new().unwrap();
let sync = KeySynchronizer::new(&config_at(&temp_dir)).await.unwrap();
let peer = iroh::SecretKey::generate().public();
sync.add_trusted_peer(peer, sync.signing_key().verifying_key())
.await
.unwrap();
let bytes = sync.export_sync_config().await.unwrap();
assert!(!bytes.is_empty());
let decoded: SyncExportConfig = postcard::from_bytes(&bytes).unwrap();
assert_eq!(decoded.node_id, sync.node_id());
assert!(decoded.trusted_peers.contains_key(&peer));
}
}