//! Performance Benchmarks for Linux Hello //! //! This module contains benchmarks for critical authentication pipeline components: //! - Face detection //! - Embedding extraction //! - Template matching (cosine similarity) //! - Anti-spoofing checks //! - Encryption/decryption (AES-GCM) //! - Secure memory operations //! //! Run with: cargo bench -p linux-hello-daemon use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId, Throughput}; use image::GrayImage; use linux_hello_daemon::detection::{detect_face_simple, SimpleFaceDetector, FaceDetect}; use linux_hello_daemon::embedding::{ cosine_similarity, euclidean_distance, PlaceholderEmbeddingExtractor, EmbeddingExtractor, }; use linux_hello_daemon::matching::match_template; use linux_hello_daemon::anti_spoofing::{ AntiSpoofingConfig, AntiSpoofingDetector, AntiSpoofingFrame, }; use linux_hello_daemon::secure_memory::{SecureEmbedding, SecureBytes, memory_protection}; use linux_hello_common::FaceTemplate; // ============================================================================ // Test Data Generation Helpers // ============================================================================ /// Generate a synthetic grayscale image with realistic noise fn generate_test_image(width: u32, height: u32) -> Vec { let mut image = Vec::with_capacity((width * height) as usize); for y in 0..height { for x in 0..width { // Generate a pattern that simulates face-like brightness distribution let center_x = width as f32 / 2.0; let center_y = height as f32 / 2.0; let dx = (x as f32 - center_x) / center_x; let dy = (y as f32 - center_y) / center_y; let dist = (dx * dx + dy * dy).sqrt(); // Face-like brightness: brighter in center, darker at edges let base = (180.0 - dist * 80.0).max(50.0); // Add some noise let noise = ((x * 17 + y * 31) % 20) as f32 - 10.0; let pixel = (base + noise).clamp(0.0, 255.0) as u8; image.push(pixel); } } image } /// Generate a normalized embedding vector fn generate_test_embedding(dimension: usize) -> Vec { let mut embedding: Vec = (0..dimension) .map(|i| ((i as f32 * 0.1).sin() + 0.5) / dimension as f32) .collect(); // Normalize let norm: f32 = embedding.iter().map(|x| x * x).sum::().sqrt(); if norm > 0.0 { for val in &mut embedding { *val /= norm; } } embedding } /// Generate test face templates fn generate_test_templates(count: usize, dimension: usize) -> Vec { (0..count) .map(|i| { let mut embedding = generate_test_embedding(dimension); // Add slight variation to each template for (j, val) in embedding.iter_mut().enumerate() { *val += (i as f32 * 0.01 + j as f32 * 0.001).sin() * 0.1; } // Re-normalize let norm: f32 = embedding.iter().map(|x| x * x).sum::().sqrt(); for val in &mut embedding { *val /= norm; } FaceTemplate { user: format!("user_{}", i), label: "default".to_string(), embedding, enrolled_at: 1234567890 + i as u64, frame_count: 5, } }) .collect() } // ============================================================================ // Face Detection Benchmarks // ============================================================================ fn bench_face_detection(c: &mut Criterion) { let mut group = c.benchmark_group("face_detection"); // Test at common camera resolutions let resolutions = [ (320, 240, "QVGA"), (640, 480, "VGA"), (1280, 720, "720p"), (1920, 1080, "1080p"), ]; for (width, height, name) in resolutions { let image = generate_test_image(width, height); let _pixels = (width * height) as u64; group.throughput(Throughput::Elements(1)); // 1 frame per iteration group.bench_with_input( BenchmarkId::new("simple_detection", name), &(image.clone(), width, height), |b, (img, w, h)| { b.iter(|| { detect_face_simple(black_box(img), black_box(*w), black_box(*h)) }); }, ); // SimpleFaceDetector (trait implementation) let detector = SimpleFaceDetector::new(0.3); group.bench_with_input( BenchmarkId::new("detector_trait", name), &(image.clone(), width, height), |b, (img, w, h)| { b.iter(|| { detector.detect(black_box(img), black_box(*w), black_box(*h)) }); }, ); } group.finish(); } // ============================================================================ // Embedding Extraction Benchmarks // ============================================================================ fn bench_embedding_extraction(c: &mut Criterion) { let mut group = c.benchmark_group("embedding_extraction"); let face_sizes = [ (64, 64, "64x64"), (112, 112, "112x112"), (160, 160, "160x160"), (224, 224, "224x224"), ]; let extractor = PlaceholderEmbeddingExtractor::new(128); for (width, height, name) in face_sizes { let image_data = generate_test_image(width, height); let face_image = GrayImage::from_raw(width, height, image_data) .expect("Failed to create test image"); group.throughput(Throughput::Elements(1)); // 1 embedding per iteration group.bench_with_input( BenchmarkId::new("placeholder_extractor", name), &face_image, |b, img| { b.iter(|| { extractor.extract(black_box(img)) }); }, ); } // Also benchmark different embedding dimensions let dimensions = [64, 128, 256, 512]; let face_image = GrayImage::from_raw(112, 112, generate_test_image(112, 112)) .expect("Failed to create test image"); for dim in dimensions { let extractor = PlaceholderEmbeddingExtractor::new(dim); group.bench_with_input( BenchmarkId::new("dimension", dim), &face_image, |b, img| { b.iter(|| { extractor.extract(black_box(img)) }); }, ); } group.finish(); } // ============================================================================ // Template Matching Benchmarks (Cosine Similarity) // ============================================================================ fn bench_template_matching(c: &mut Criterion) { let mut group = c.benchmark_group("template_matching"); // Benchmark cosine similarity at different dimensions let dimensions = [64, 128, 256, 512, 1024]; for dim in dimensions { let emb_a = generate_test_embedding(dim); let emb_b = generate_test_embedding(dim); group.throughput(Throughput::Elements(1)); // 1 comparison per iteration group.bench_with_input( BenchmarkId::new("cosine_similarity", dim), &(emb_a.clone(), emb_b.clone()), |b, (a, bb)| { b.iter(|| { cosine_similarity(black_box(a), black_box(bb)) }); }, ); group.bench_with_input( BenchmarkId::new("euclidean_distance", dim), &(emb_a.clone(), emb_b.clone()), |b, (a, bb)| { b.iter(|| { euclidean_distance(black_box(a), black_box(bb)) }); }, ); } // Benchmark matching against template databases of different sizes let template_counts = [1, 5, 10, 50, 100]; let query = generate_test_embedding(128); for count in template_counts { let templates = generate_test_templates(count, 128); group.throughput(Throughput::Elements(count as u64)); // N comparisons group.bench_with_input( BenchmarkId::new("match_against_n_templates", count), &(query.clone(), templates), |b, (q, tmpl)| { b.iter(|| { match_template(black_box(q), black_box(tmpl), 0.4) }); }, ); } group.finish(); } // ============================================================================ // Anti-Spoofing Benchmarks // ============================================================================ fn bench_anti_spoofing(c: &mut Criterion) { let mut group = c.benchmark_group("anti_spoofing"); let resolutions = [ (320, 240, "QVGA"), (640, 480, "VGA"), ]; for (width, height, name) in resolutions { let pixels = generate_test_image(width, height); let frame = AntiSpoofingFrame { pixels: pixels.clone(), width, height, is_ir: true, face_bbox: Some((width / 4, height / 4, width / 2, height / 2)), timestamp_ms: 0, }; // Single frame check group.throughput(Throughput::Elements(1)); group.bench_with_input( BenchmarkId::new("single_frame_check", name), &frame, |b, f| { let config = AntiSpoofingConfig::default(); let mut detector = AntiSpoofingDetector::new(config); b.iter(|| { detector.reset(); detector.check_frame(black_box(f)) }); }, ); } // Full pipeline with temporal analysis let width = 640; let height = 480; let frames: Vec<_> = (0..10) .map(|i| { let pixels = generate_test_image(width, height); AntiSpoofingFrame { pixels, width, height, is_ir: true, face_bbox: Some((width / 4 + i, height / 4, width / 2, height / 2)), timestamp_ms: i as u64 * 100, } }) .collect(); group.throughput(Throughput::Elements(10)); // 10 frames group.bench_with_input( BenchmarkId::new("full_pipeline", "10_frames"), &frames, |b, frames| { let mut config = AntiSpoofingConfig::default(); config.enable_movement_check = true; config.enable_blink_check = true; b.iter(|| { let mut detector = AntiSpoofingDetector::new(config.clone()); let mut last_result = None; for frame in frames { last_result = Some(detector.check_frame(black_box(frame))); } last_result }); }, ); group.finish(); } // ============================================================================ // Encryption/Decryption Benchmarks (AES-GCM) // ============================================================================ fn bench_encryption(c: &mut Criterion) { use linux_hello_daemon::tpm::SoftwareTpmFallback; use linux_hello_daemon::tpm::TpmStorage; let mut group = c.benchmark_group("encryption"); // Initialize software TPM fallback in temp directory let temp_dir = std::env::temp_dir().join("linux-hello-bench-keys"); let _ = std::fs::create_dir_all(&temp_dir); let mut storage = SoftwareTpmFallback::new(&temp_dir); if storage.initialize().is_err() { // Skip encryption benchmarks if we can't initialize eprintln!("Warning: Could not initialize encryption storage for benchmarks"); group.finish(); return; } // Test with different data sizes (embedding sizes) let data_sizes = [ (128 * 4, "128_floats"), // 128-dim embedding (256 * 4, "256_floats"), // 256-dim embedding (512 * 4, "512_floats"), // 512-dim embedding (1024 * 4, "1024_floats"), // 1024-dim embedding ]; for (size, name) in data_sizes { let plaintext: Vec = (0..size).map(|i| (i % 256) as u8).collect(); group.throughput(Throughput::Bytes(size as u64)); group.bench_with_input( BenchmarkId::new("encrypt", name), &plaintext, |b, data| { b.iter(|| { storage.encrypt("bench_user", black_box(data)) }); }, ); // Encrypt once for decrypt benchmark let encrypted = storage.encrypt("bench_user", &plaintext) .expect("Encryption failed"); group.bench_with_input( BenchmarkId::new("decrypt", name), &encrypted, |b, enc| { b.iter(|| { storage.decrypt("bench_user", black_box(enc)) }); }, ); // Round-trip benchmark group.bench_with_input( BenchmarkId::new("round_trip", name), &plaintext, |b, data| { b.iter(|| { let enc = storage.encrypt("bench_user", black_box(data)).unwrap(); storage.decrypt("bench_user", black_box(&enc)) }); }, ); } // Cleanup let _ = std::fs::remove_dir_all(&temp_dir); group.finish(); } // ============================================================================ // Secure Memory Operation Benchmarks // ============================================================================ fn bench_secure_memory(c: &mut Criterion) { let mut group = c.benchmark_group("secure_memory"); // SecureEmbedding creation and operations let dimensions = [128, 256, 512]; for dim in dimensions { let data = generate_test_embedding(dim); group.throughput(Throughput::Elements(dim as u64)); // SecureEmbedding creation (includes memory locking attempt) group.bench_with_input( BenchmarkId::new("secure_embedding_create", dim), &data, |b, d| { b.iter(|| { SecureEmbedding::new(black_box(d.clone())) }); }, ); // Secure cosine similarity let secure_a = SecureEmbedding::new(data.clone()); let secure_b = SecureEmbedding::new(generate_test_embedding(dim)); group.bench_with_input( BenchmarkId::new("secure_cosine_similarity", dim), &(secure_a.clone(), secure_b.clone()), |b, (a, bb)| { b.iter(|| { a.cosine_similarity(black_box(bb)) }); }, ); // Serialization/deserialization group.bench_with_input( BenchmarkId::new("secure_to_bytes", dim), &secure_a, |b, emb| { b.iter(|| { emb.to_bytes() }); }, ); let bytes = secure_a.to_bytes(); group.bench_with_input( BenchmarkId::new("secure_from_bytes", dim), &bytes, |b, data| { b.iter(|| { SecureEmbedding::from_bytes(black_box(data)) }); }, ); } // SecureBytes constant-time comparison let byte_sizes = [64, 128, 256, 512, 1024]; for size in byte_sizes { let bytes_a = SecureBytes::new((0..size).map(|i| (i % 256) as u8).collect()); let bytes_b = SecureBytes::new((0..size).map(|i| (i % 256) as u8).collect()); let bytes_diff = SecureBytes::new((0..size).map(|i| ((i + 1) % 256) as u8).collect()); group.throughput(Throughput::Bytes(size as u64)); // Equal bytes comparison group.bench_with_input( BenchmarkId::new("constant_time_eq_match", size), &(bytes_a.clone(), bytes_b.clone()), |b, (a, bb)| { b.iter(|| { a.constant_time_eq(black_box(bb)) }); }, ); // Different bytes comparison (should take same time) group.bench_with_input( BenchmarkId::new("constant_time_eq_differ", size), &(bytes_a.clone(), bytes_diff.clone()), |b, (a, d)| { b.iter(|| { a.constant_time_eq(black_box(d)) }); }, ); } // Memory zeroization for size in byte_sizes { group.throughput(Throughput::Bytes(size as u64)); group.bench_with_input( BenchmarkId::new("secure_zero", size), &size, |b, &sz| { let mut buffer: Vec = (0..sz).map(|i| (i % 256) as u8).collect(); b.iter(|| { memory_protection::secure_zero(black_box(&mut buffer)); }); }, ); } group.finish(); } // ============================================================================ // Full Pipeline Benchmark (End-to-End) // ============================================================================ fn bench_full_pipeline(c: &mut Criterion) { let mut group = c.benchmark_group("full_pipeline"); group.sample_size(50); // Fewer samples for slower benchmarks // Simulate complete authentication flow let width = 640; let height = 480; let image_data = generate_test_image(width, height); let templates = generate_test_templates(5, 128); group.throughput(Throughput::Elements(1)); // 1 authentication attempt group.bench_function("auth_pipeline_no_crypto", |b| { let detector = SimpleFaceDetector::new(0.3); let extractor = PlaceholderEmbeddingExtractor::new(128); b.iter(|| { // Step 1: Face detection let detections = detector.detect( black_box(&image_data), black_box(width), black_box(height) ).unwrap(); if let Some(detection) = detections.first() { // Step 2: Extract face region (simulated) let (_x, _y, w, h) = detection.to_pixels(width, height); let face_image = GrayImage::from_raw(w, h, vec![128u8; (w * h) as usize]) .unwrap_or_else(|| GrayImage::new(w, h)); // Step 3: Extract embedding let embedding = extractor.extract(&face_image).unwrap(); // Step 4: Template matching let result = match_template(&embedding, &templates, 0.4); black_box(Some(result)) } else { black_box(None) } }); }); // With anti-spoofing group.bench_function("auth_pipeline_with_antispoofing", |b| { let detector = SimpleFaceDetector::new(0.3); let extractor = PlaceholderEmbeddingExtractor::new(128); let config = AntiSpoofingConfig::default(); b.iter(|| { let mut spoof_detector = AntiSpoofingDetector::new(config.clone()); // Step 1: Face detection let detections = detector.detect( black_box(&image_data), black_box(width), black_box(height) ).unwrap(); if let Some(detection) = detections.first() { // Step 2: Anti-spoofing check let frame = AntiSpoofingFrame { pixels: image_data.clone(), width, height, is_ir: true, face_bbox: Some(detection.to_pixels(width, height)), timestamp_ms: 0, }; let liveness = spoof_detector.check_frame(&frame).unwrap(); if liveness.is_live { // Step 3: Extract face region let (_, _, w, h) = detection.to_pixels(width, height); let face_image = GrayImage::from_raw(w, h, vec![128u8; (w * h) as usize]) .unwrap_or_else(|| GrayImage::new(w, h)); // Step 4: Extract embedding let embedding = extractor.extract(&face_image).unwrap(); // Step 5: Template matching let result = match_template(&embedding, &templates, 0.4); black_box(Some(result)) } else { black_box(None) } } else { black_box(None) } }); }); group.finish(); } // ============================================================================ // Criterion Configuration and Main // ============================================================================ criterion_group!( benches, bench_face_detection, bench_embedding_extraction, bench_template_matching, bench_anti_spoofing, bench_encryption, bench_secure_memory, bench_full_pipeline, ); criterion_main!(benches);