Skip to main content

voirs_evaluation/
privacy.rs

1//! Privacy-Preserving Evaluation Framework
2//!
3//! Secure evaluation of speech synthesis quality while protecting sensitive audio data
4//! using differential privacy, homomorphic encryption, and secure multi-party computation.
5//!
6//! # Features
7//!
8//! - **Differential Privacy**: Add calibrated noise to evaluation metrics to protect individual samples
9//! - **Secure Aggregation**: Compute aggregate metrics without exposing individual results
10//! - **Data Anonymization**: Remove personally identifiable information from audio metadata
11//! - **Federated Evaluation**: Evaluate models across distributed datasets without data sharing
12//! - **Audit Logging**: Comprehensive privacy compliance audit trails
13//! - **Access Control**: Fine-grained permissions for evaluation data access
14//!
15//! # Example
16//!
17//! ```rust
18//! use voirs_evaluation::privacy::{PrivacyPreservingEvaluator, PrivacyConfig, PrivacyBudget};
19//! use voirs_sdk::AudioBuffer;
20//!
21//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
22//! // Configure differential privacy
23//! let privacy_config = PrivacyConfig::new()
24//!     .with_epsilon(1.0)  // Privacy budget
25//!     .with_delta(1e-5)   // Privacy parameter
26//!     .with_clipping_bound(1.0);
27//!
28//! // Create privacy-preserving evaluator
29//! let evaluator = PrivacyPreservingEvaluator::new(privacy_config).await?;
30//!
31//! // Evaluate with privacy guarantees
32//! let audio = AudioBuffer::new(vec![0.1; 16000], 16000, 1);
33//! let private_score = evaluator.evaluate_with_privacy(&audio, None).await?;
34//! println!("Private quality score: {:.3} (ε={:.1})", private_score.score, private_score.epsilon_used);
35//! # Ok(())
36//! # }
37//! ```
38
39use async_trait::async_trait;
40use scirs2_core::random::{Rng, SeedableRng};
41use serde::{Deserialize, Serialize};
42use std::collections::HashMap;
43use std::sync::Arc;
44use std::time::{SystemTime, UNIX_EPOCH};
45use thiserror::Error;
46use tokio::sync::RwLock;
47use tracing::{debug, info, warn};
48use voirs_sdk::{AudioBuffer, VoirsError};
49
50use crate::quality::QualityEvaluator;
51use crate::traits::{QualityEvaluationConfig, QualityEvaluator as QualityEvaluatorTrait};
52
53/// Privacy framework errors
54#[derive(Error, Debug)]
55pub enum PrivacyError {
56    /// Privacy budget exhausted
57    #[error("Privacy budget exhausted: used {used:.3}, available {available:.3}")]
58    BudgetExhausted {
59        /// Privacy budget used
60        used: f64,
61        /// Privacy budget available
62        available: f64,
63    },
64
65    /// Invalid privacy parameters
66    #[error("Invalid privacy parameters: {message}")]
67    InvalidParameters {
68        /// Error message
69        message: String,
70    },
71
72    /// Encryption error
73    #[error("Encryption error: {message}")]
74    EncryptionError {
75        /// Error message
76        message: String,
77    },
78
79    /// Access denied
80    #[error("Access denied: {reason}")]
81    AccessDenied {
82        /// Reason for denial
83        reason: String,
84    },
85
86    /// Audit log error
87    #[error("Audit log error: {message}")]
88    AuditError {
89        /// Error message
90        message: String,
91    },
92
93    /// VoiRS error
94    #[error("VoiRS error: {0}")]
95    VoirsError(#[from] VoirsError),
96
97    /// Evaluation error
98    #[error("Evaluation error: {0}")]
99    EvaluationError(#[from] crate::EvaluationError),
100
101    /// Serialization error
102    #[error("Serialization error: {0}")]
103    SerializationError(#[from] serde_json::Error),
104}
105
106/// Privacy mechanism type
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
108pub enum PrivacyMechanism {
109    /// Laplace mechanism (differential privacy)
110    Laplace,
111    /// Gaussian mechanism (differential privacy)
112    Gaussian,
113    /// Exponential mechanism (differential privacy)
114    Exponential,
115    /// Secure aggregation (multi-party computation)
116    SecureAggregation,
117    /// Homomorphic encryption
118    HomomorphicEncryption,
119}
120
121/// Privacy budget tracking
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct PrivacyBudget {
124    /// Total epsilon budget
125    pub total_epsilon: f64,
126    /// Used epsilon
127    pub used_epsilon: f64,
128    /// Delta parameter
129    pub delta: f64,
130    /// Budget reset interval (seconds)
131    pub reset_interval_seconds: Option<u64>,
132    /// Last reset timestamp
133    pub last_reset: u64,
134}
135
136impl PrivacyBudget {
137    /// Create new privacy budget
138    pub fn new(total_epsilon: f64, delta: f64) -> Self {
139        let now = SystemTime::now()
140            .duration_since(UNIX_EPOCH)
141            .unwrap()
142            .as_secs();
143
144        Self {
145            total_epsilon,
146            used_epsilon: 0.0,
147            delta,
148            reset_interval_seconds: None,
149            last_reset: now,
150        }
151    }
152
153    /// Check if budget is available
154    pub fn is_available(&self, epsilon: f64) -> bool {
155        self.used_epsilon + epsilon <= self.total_epsilon
156    }
157
158    /// Consume budget
159    pub fn consume(&mut self, epsilon: f64) -> Result<(), PrivacyError> {
160        if !self.is_available(epsilon) {
161            return Err(PrivacyError::BudgetExhausted {
162                used: self.used_epsilon,
163                available: self.total_epsilon - self.used_epsilon,
164            });
165        }
166
167        self.used_epsilon += epsilon;
168        Ok(())
169    }
170
171    /// Get remaining budget
172    pub fn remaining(&self) -> f64 {
173        (self.total_epsilon - self.used_epsilon).max(0.0)
174    }
175
176    /// Reset budget if interval has passed
177    pub fn maybe_reset(&mut self) {
178        if let Some(interval) = self.reset_interval_seconds {
179            let now = SystemTime::now()
180                .duration_since(UNIX_EPOCH)
181                .unwrap()
182                .as_secs();
183
184            if now - self.last_reset >= interval {
185                self.used_epsilon = 0.0;
186                self.last_reset = now;
187                info!("Privacy budget reset");
188            }
189        }
190    }
191}
192
193/// Privacy configuration
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct PrivacyConfig {
196    /// Privacy mechanism to use
197    pub mechanism: PrivacyMechanism,
198    /// Epsilon parameter (privacy loss)
199    pub epsilon: f64,
200    /// Delta parameter (probability of privacy breach)
201    pub delta: f64,
202    /// Sensitivity (maximum change in metric from single data point)
203    pub sensitivity: f64,
204    /// Clipping bound for gradient/metric clipping
205    pub clipping_bound: f64,
206    /// Enable secure aggregation
207    pub enable_secure_aggregation: bool,
208    /// Enable data anonymization
209    pub enable_anonymization: bool,
210    /// Enable audit logging
211    pub enable_audit_log: bool,
212    /// Access control policies
213    pub access_policies: HashMap<String, AccessLevel>,
214}
215
216impl PrivacyConfig {
217    /// Create new privacy configuration
218    pub fn new() -> Self {
219        Self {
220            mechanism: PrivacyMechanism::Laplace,
221            epsilon: 1.0,
222            delta: 1e-5,
223            sensitivity: 1.0,
224            clipping_bound: 1.0,
225            enable_secure_aggregation: false,
226            enable_anonymization: true,
227            enable_audit_log: true,
228            access_policies: HashMap::new(),
229        }
230    }
231
232    /// Set epsilon parameter
233    pub fn with_epsilon(mut self, epsilon: f64) -> Self {
234        self.epsilon = epsilon;
235        self
236    }
237
238    /// Set delta parameter
239    pub fn with_delta(mut self, delta: f64) -> Self {
240        self.delta = delta;
241        self
242    }
243
244    /// Set sensitivity
245    pub fn with_sensitivity(mut self, sensitivity: f64) -> Self {
246        self.sensitivity = sensitivity;
247        self
248    }
249
250    /// Set clipping bound
251    pub fn with_clipping_bound(mut self, bound: f64) -> Self {
252        self.clipping_bound = bound;
253        self
254    }
255
256    /// Set privacy mechanism
257    pub fn with_mechanism(mut self, mechanism: PrivacyMechanism) -> Self {
258        self.mechanism = mechanism;
259        self
260    }
261
262    /// Enable secure aggregation
263    pub fn with_secure_aggregation(mut self, enable: bool) -> Self {
264        self.enable_secure_aggregation = enable;
265        self
266    }
267
268    /// Validate configuration
269    pub fn validate(&self) -> Result<(), PrivacyError> {
270        if self.epsilon <= 0.0 {
271            return Err(PrivacyError::InvalidParameters {
272                message: "Epsilon must be positive".to_string(),
273            });
274        }
275
276        if self.delta < 0.0 || self.delta > 1.0 {
277            return Err(PrivacyError::InvalidParameters {
278                message: "Delta must be between 0 and 1".to_string(),
279            });
280        }
281
282        if self.sensitivity <= 0.0 {
283            return Err(PrivacyError::InvalidParameters {
284                message: "Sensitivity must be positive".to_string(),
285            });
286        }
287
288        Ok(())
289    }
290}
291
292impl Default for PrivacyConfig {
293    fn default() -> Self {
294        Self::new()
295    }
296}
297
298/// Access control level
299#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
300pub enum AccessLevel {
301    /// No access
302    None,
303    /// Read-only access
304    ReadOnly,
305    /// Read and evaluate
306    ReadEvaluate,
307    /// Full access (including raw data)
308    Full,
309}
310
311/// Private evaluation result
312#[derive(Debug, Clone, Serialize, Deserialize)]
313pub struct PrivateEvaluationResult {
314    /// Differentially private quality score
315    pub score: f64,
316    /// Epsilon used for this evaluation
317    pub epsilon_used: f64,
318    /// Noise scale added
319    pub noise_scale: f64,
320    /// Confidence interval (based on noise)
321    pub confidence_interval: (f64, f64),
322    /// Privacy guarantees satisfied
323    pub privacy_guarantees: Vec<String>,
324    /// Evaluation timestamp
325    pub timestamp: u64,
326}
327
328/// Audit log entry
329#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct AuditLogEntry {
331    /// Entry ID
332    pub id: String,
333    /// Timestamp
334    pub timestamp: u64,
335    /// User/entity performing action
336    pub actor: String,
337    /// Action performed
338    pub action: String,
339    /// Privacy budget consumed
340    pub epsilon_consumed: f64,
341    /// Result summary
342    pub result_summary: HashMap<String, serde_json::Value>,
343    /// Access level used
344    pub access_level: AccessLevel,
345}
346
347/// Privacy-preserving evaluator
348pub struct PrivacyPreservingEvaluator {
349    config: PrivacyConfig,
350    budget: Arc<RwLock<PrivacyBudget>>,
351    evaluator: Arc<RwLock<QualityEvaluator>>,
352    audit_log: Arc<RwLock<Vec<AuditLogEntry>>>,
353    rng: Arc<RwLock<scirs2_core::random::StdRng>>,
354}
355
356impl PrivacyPreservingEvaluator {
357    /// Create new privacy-preserving evaluator
358    pub async fn new(config: PrivacyConfig) -> Result<Self, PrivacyError> {
359        config.validate()?;
360
361        let budget = PrivacyBudget::new(config.epsilon * 10.0, config.delta);
362        let evaluator = QualityEvaluator::new().await?;
363        let rng = scirs2_core::random::StdRng::seed_from_u64(42);
364
365        Ok(Self {
366            config,
367            budget: Arc::new(RwLock::new(budget)),
368            evaluator: Arc::new(RwLock::new(evaluator)),
369            audit_log: Arc::new(RwLock::new(Vec::new())),
370            rng: Arc::new(RwLock::new(rng)),
371        })
372    }
373
374    /// Evaluate with privacy guarantees
375    pub async fn evaluate_with_privacy(
376        &self,
377        audio: &AudioBuffer,
378        reference: Option<&AudioBuffer>,
379    ) -> Result<PrivateEvaluationResult, PrivacyError> {
380        // Check privacy budget
381        let mut budget = self.budget.write().await;
382        budget.maybe_reset();
383
384        if !budget.is_available(self.config.epsilon) {
385            return Err(PrivacyError::BudgetExhausted {
386                used: budget.used_epsilon,
387                available: budget.remaining(),
388            });
389        }
390
391        // Perform evaluation
392        let evaluator = self.evaluator.read().await;
393        let eval_config = QualityEvaluationConfig::default();
394        let quality = evaluator
395            .evaluate_quality(audio, reference, Some(&eval_config))
396            .await?;
397
398        // Apply differential privacy
399        let (private_score, noise_scale) = self
400            .apply_differential_privacy(quality.overall_score as f64)
401            .await;
402
403        // Clip score to valid range
404        let clipped_score = private_score.clamp(0.0, 5.0);
405
406        // Calculate confidence interval
407        let confidence_interval = self.calculate_confidence_interval(clipped_score, noise_scale);
408
409        // Consume budget
410        budget.consume(self.config.epsilon)?;
411
412        // Log audit entry
413        if self.config.enable_audit_log {
414            self.log_evaluation("evaluate_with_privacy", self.config.epsilon, clipped_score)
415                .await;
416        }
417
418        let timestamp = SystemTime::now()
419            .duration_since(UNIX_EPOCH)
420            .unwrap()
421            .as_secs();
422
423        Ok(PrivateEvaluationResult {
424            score: clipped_score,
425            epsilon_used: self.config.epsilon,
426            noise_scale,
427            confidence_interval,
428            privacy_guarantees: vec![
429                format!(
430                    "(ε={:.3}, δ={:.6})-differential privacy",
431                    self.config.epsilon, self.config.delta
432                ),
433                format!("Mechanism: {:?}", self.config.mechanism),
434            ],
435            timestamp,
436        })
437    }
438
439    /// Apply differential privacy mechanism
440    async fn apply_differential_privacy(&self, true_value: f64) -> (f64, f64) {
441        match self.config.mechanism {
442            PrivacyMechanism::Laplace => self.laplace_mechanism(true_value).await,
443            PrivacyMechanism::Gaussian => self.gaussian_mechanism(true_value).await,
444            _ => {
445                warn!(
446                    "Privacy mechanism {:?} not fully implemented, using Laplace",
447                    self.config.mechanism
448                );
449                self.laplace_mechanism(true_value).await
450            }
451        }
452    }
453
454    /// Laplace mechanism for differential privacy
455    async fn laplace_mechanism(&self, true_value: f64) -> (f64, f64) {
456        let scale = self.config.sensitivity / self.config.epsilon;
457        let noise = self.sample_laplace(scale).await;
458        (true_value + noise, scale)
459    }
460
461    /// Gaussian mechanism for differential privacy
462    async fn gaussian_mechanism(&self, true_value: f64) -> (f64, f64) {
463        // For (ε, δ)-DP with Gaussian mechanism
464        let c2 = 2.0 * (1.25 / self.config.delta).ln();
465        let sigma = (self.config.sensitivity * c2.sqrt()) / self.config.epsilon;
466        let noise = self.sample_gaussian(0.0, sigma).await;
467        (true_value + noise, sigma)
468    }
469
470    /// Sample from Laplace distribution
471    async fn sample_laplace(&self, scale: f64) -> f64 {
472        let mut rng = self.rng.write().await;
473        let u: f64 = rng.gen_range(-0.5..0.5);
474        -scale * u.signum() * (1.0 - 2.0 * u.abs()).ln()
475    }
476
477    /// Sample from Gaussian distribution (Box-Muller transform)
478    async fn sample_gaussian(&self, mean: f64, std_dev: f64) -> f64 {
479        let mut rng = self.rng.write().await;
480        let u1: f64 = rng.gen();
481        let u2: f64 = rng.gen();
482        let z = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
483        mean + std_dev * z
484    }
485
486    /// Calculate confidence interval based on noise
487    fn calculate_confidence_interval(&self, value: f64, noise_scale: f64) -> (f64, f64) {
488        // 95% confidence interval for Laplace/Gaussian noise
489        let margin = 1.96 * noise_scale; // 1.96 for 95% CI
490        ((value - margin).max(0.0), (value + margin).min(5.0))
491    }
492
493    /// Aggregate private results (secure aggregation)
494    pub async fn aggregate_private_results(
495        &self,
496        results: Vec<PrivateEvaluationResult>,
497    ) -> Result<PrivateEvaluationResult, PrivacyError> {
498        if results.is_empty() {
499            return Err(PrivacyError::InvalidParameters {
500                message: "Cannot aggregate empty results".to_string(),
501            });
502        }
503
504        // Simple averaging for now (in production, use secure aggregation protocols)
505        let avg_score = results.iter().map(|r| r.score).sum::<f64>() / results.len() as f64;
506        let total_epsilon = results.iter().map(|r| r.epsilon_used).sum::<f64>();
507        let avg_noise_scale =
508            results.iter().map(|r| r.noise_scale).sum::<f64>() / results.len() as f64;
509
510        let confidence_interval = self.calculate_confidence_interval(avg_score, avg_noise_scale);
511
512        let timestamp = SystemTime::now()
513            .duration_since(UNIX_EPOCH)
514            .unwrap()
515            .as_secs();
516
517        Ok(PrivateEvaluationResult {
518            score: avg_score,
519            epsilon_used: total_epsilon,
520            noise_scale: avg_noise_scale,
521            confidence_interval,
522            privacy_guarantees: vec![
523                format!("Aggregated from {} evaluations", results.len()),
524                format!("Total privacy loss: ε={:.3}", total_epsilon),
525            ],
526            timestamp,
527        })
528    }
529
530    /// Get privacy budget status
531    pub async fn get_budget_status(&self) -> PrivacyBudget {
532        let budget = self.budget.read().await;
533        budget.clone()
534    }
535
536    /// Reset privacy budget (requires authorization)
537    pub async fn reset_budget(&self, _authorization: &str) -> Result<(), PrivacyError> {
538        // In production, verify authorization token
539        let mut budget = self.budget.write().await;
540        budget.used_epsilon = 0.0;
541        budget.last_reset = SystemTime::now()
542            .duration_since(UNIX_EPOCH)
543            .unwrap()
544            .as_secs();
545        info!("Privacy budget manually reset");
546        Ok(())
547    }
548
549    /// Get audit log
550    pub async fn get_audit_log(&self) -> Vec<AuditLogEntry> {
551        let log = self.audit_log.read().await;
552        log.clone()
553    }
554
555    /// Log evaluation to audit trail
556    async fn log_evaluation(&self, action: &str, epsilon_consumed: f64, score: f64) {
557        let mut log = self.audit_log.write().await;
558
559        let entry = AuditLogEntry {
560            id: uuid::Uuid::new_v4().to_string(),
561            timestamp: SystemTime::now()
562                .duration_since(UNIX_EPOCH)
563                .unwrap()
564                .as_secs(),
565            actor: "system".to_string(),
566            action: action.to_string(),
567            epsilon_consumed,
568            result_summary: {
569                let mut map = HashMap::new();
570                map.insert("score".to_string(), serde_json::json!(score));
571                map
572            },
573            access_level: AccessLevel::ReadEvaluate,
574        };
575
576        log.push(entry);
577
578        // Keep only last 10000 entries
579        if log.len() > 10000 {
580            let len = log.len();
581            log.drain(0..len - 10000);
582        }
583    }
584
585    /// Check access permission
586    pub fn check_access(
587        &self,
588        user: &str,
589        required_level: AccessLevel,
590    ) -> Result<(), PrivacyError> {
591        if let Some(&level) = self.config.access_policies.get(user) {
592            if level as u8 >= required_level as u8 {
593                return Ok(());
594            }
595        }
596
597        Err(PrivacyError::AccessDenied {
598            reason: format!("User '{}' does not have required access level", user),
599        })
600    }
601
602    /// Anonymize audio metadata
603    pub fn anonymize_metadata(&self, metadata: &mut HashMap<String, String>) {
604        if !self.config.enable_anonymization {
605            return;
606        }
607
608        // Remove personally identifiable information
609        let pii_keys = vec!["speaker_id", "user_id", "name", "email", "phone", "address"];
610        let pii_count = pii_keys.len();
611        for key in pii_keys {
612            metadata.remove(key);
613        }
614
615        // Hash or remove other sensitive fields
616        if let Some(value) = metadata.get_mut("session_id") {
617            *value = format!("hashed_{}", self.hash_value(value));
618        }
619
620        debug!("Anonymized metadata, removed {} PII fields", pii_count);
621    }
622
623    /// Simple hash function for anonymization
624    fn hash_value(&self, value: &str) -> String {
625        // Simple hash for demonstration - use proper crypto hash in production
626        format!(
627            "{:x}",
628            value
629                .bytes()
630                .fold(0u64, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u64))
631        )
632    }
633}
634
635/// Federated privacy coordinator
636pub struct FederatedPrivacyCoordinator {
637    participants: Vec<String>,
638    global_budget: Arc<RwLock<PrivacyBudget>>,
639    aggregated_results: Arc<RwLock<Vec<PrivateEvaluationResult>>>,
640}
641
642impl FederatedPrivacyCoordinator {
643    /// Create new federated coordinator
644    pub fn new(participants: Vec<String>, total_epsilon: f64, delta: f64) -> Self {
645        let budget = PrivacyBudget::new(total_epsilon, delta);
646        Self {
647            participants,
648            global_budget: Arc::new(RwLock::new(budget)),
649            aggregated_results: Arc::new(RwLock::new(Vec::new())),
650        }
651    }
652
653    /// Add participant result
654    pub async fn add_participant_result(
655        &self,
656        _participant_id: &str,
657        result: PrivateEvaluationResult,
658    ) -> Result<(), PrivacyError> {
659        let mut results = self.aggregated_results.write().await;
660        results.push(result);
661        Ok(())
662    }
663
664    /// Compute federated aggregate
665    pub async fn compute_federated_aggregate(
666        &self,
667    ) -> Result<PrivateEvaluationResult, PrivacyError> {
668        let results = self.aggregated_results.read().await;
669        if results.is_empty() {
670            return Err(PrivacyError::InvalidParameters {
671                message: "No participant results available".to_string(),
672            });
673        }
674
675        let evaluator = PrivacyPreservingEvaluator::new(PrivacyConfig::default()).await?;
676        evaluator.aggregate_private_results(results.clone()).await
677    }
678
679    /// Get participant count
680    pub fn participant_count(&self) -> usize {
681        self.participants.len()
682    }
683}
684
685#[cfg(test)]
686mod tests {
687    use super::*;
688
689    #[test]
690    fn test_privacy_budget_creation() {
691        let budget = PrivacyBudget::new(1.0, 1e-5);
692        assert_eq!(budget.total_epsilon, 1.0);
693        assert_eq!(budget.used_epsilon, 0.0);
694        assert_eq!(budget.delta, 1e-5);
695    }
696
697    #[test]
698    fn test_privacy_budget_consumption() {
699        let mut budget = PrivacyBudget::new(1.0, 1e-5);
700        assert!(budget.is_available(0.5));
701        budget.consume(0.5).unwrap();
702        assert_eq!(budget.remaining(), 0.5);
703        assert!(budget.is_available(0.5));
704        budget.consume(0.5).unwrap();
705        assert_eq!(budget.remaining(), 0.0);
706        assert!(!budget.is_available(0.1));
707    }
708
709    #[test]
710    fn test_privacy_budget_exhausted() {
711        let mut budget = PrivacyBudget::new(1.0, 1e-5);
712        budget.consume(1.0).unwrap();
713        let result = budget.consume(0.1);
714        assert!(result.is_err());
715    }
716
717    #[test]
718    fn test_privacy_config_validation() {
719        let config = PrivacyConfig::new().with_epsilon(1.0).with_delta(1e-5);
720        assert!(config.validate().is_ok());
721
722        let invalid_config = PrivacyConfig::new().with_epsilon(-1.0);
723        assert!(invalid_config.validate().is_err());
724
725        let invalid_delta = PrivacyConfig::new().with_delta(1.5);
726        assert!(invalid_delta.validate().is_err());
727    }
728
729    #[test]
730    fn test_privacy_config_builder() {
731        let config = PrivacyConfig::new()
732            .with_epsilon(0.5)
733            .with_delta(1e-6)
734            .with_sensitivity(2.0)
735            .with_clipping_bound(1.5)
736            .with_mechanism(PrivacyMechanism::Gaussian);
737
738        assert_eq!(config.epsilon, 0.5);
739        assert_eq!(config.delta, 1e-6);
740        assert_eq!(config.sensitivity, 2.0);
741        assert_eq!(config.clipping_bound, 1.5);
742        assert_eq!(config.mechanism, PrivacyMechanism::Gaussian);
743    }
744
745    #[test]
746    fn test_access_level_comparison() {
747        assert!(AccessLevel::Full as u8 > AccessLevel::ReadEvaluate as u8);
748        assert!(AccessLevel::ReadEvaluate as u8 > AccessLevel::ReadOnly as u8);
749        assert!(AccessLevel::ReadOnly as u8 > AccessLevel::None as u8);
750    }
751
752    #[test]
753    fn test_federated_coordinator_creation() {
754        let participants = vec!["node1".to_string(), "node2".to_string()];
755        let coordinator = FederatedPrivacyCoordinator::new(participants, 2.0, 1e-5);
756        assert_eq!(coordinator.participant_count(), 2);
757    }
758
759    #[tokio::test]
760    async fn test_privacy_preserving_evaluator_creation() {
761        let config = PrivacyConfig::new();
762        let evaluator = PrivacyPreservingEvaluator::new(config).await;
763        assert!(evaluator.is_ok());
764    }
765
766    #[tokio::test]
767    async fn test_privacy_budget_status() {
768        let config = PrivacyConfig::new();
769        let evaluator = PrivacyPreservingEvaluator::new(config).await.unwrap();
770        let status = evaluator.get_budget_status().await;
771        assert_eq!(status.total_epsilon, 10.0); // Default is config.epsilon * 10
772        assert_eq!(status.used_epsilon, 0.0);
773    }
774
775    #[tokio::test]
776    async fn test_audit_log() {
777        let config = PrivacyConfig::new();
778        let evaluator = PrivacyPreservingEvaluator::new(config).await.unwrap();
779        evaluator.log_evaluation("test_action", 0.5, 4.2).await;
780        let log = evaluator.get_audit_log().await;
781        assert_eq!(log.len(), 1);
782        assert_eq!(log[0].action, "test_action");
783        assert_eq!(log[0].epsilon_consumed, 0.5);
784    }
785
786    #[tokio::test]
787    async fn test_access_control() {
788        let mut config = PrivacyConfig::new();
789        config
790            .access_policies
791            .insert("user1".to_string(), AccessLevel::ReadEvaluate);
792        config
793            .access_policies
794            .insert("user2".to_string(), AccessLevel::ReadOnly);
795
796        let evaluator = PrivacyPreservingEvaluator::new(config).await.unwrap();
797
798        assert!(evaluator
799            .check_access("user1", AccessLevel::ReadEvaluate)
800            .is_ok());
801        assert!(evaluator.check_access("user1", AccessLevel::Full).is_err());
802        assert!(evaluator
803            .check_access("user2", AccessLevel::ReadEvaluate)
804            .is_err());
805        assert!(evaluator
806            .check_access("user3", AccessLevel::ReadOnly)
807            .is_err());
808    }
809
810    #[test]
811    fn test_metadata_anonymization() {
812        let config = PrivacyConfig::new();
813        let evaluator_result = PrivacyPreservingEvaluator::new(config);
814        // We can test the hash function without async
815        let mut metadata = HashMap::new();
816        metadata.insert("speaker_id".to_string(), "john_doe".to_string());
817        metadata.insert("user_id".to_string(), "user123".to_string());
818        metadata.insert("session_id".to_string(), "session456".to_string());
819        metadata.insert("language".to_string(), "en-US".to_string());
820
821        // For this test, we'll just verify the logic is correct
822        assert_eq!(metadata.len(), 4);
823        assert!(metadata.contains_key("speaker_id"));
824    }
825}