Files
Linux-Hello/linux-hello-daemon/benches/benchmarks.rs
2026-01-15 22:40:51 +01:00

649 lines
21 KiB
Rust

//! 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<u8> {
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<f32> {
let mut embedding: Vec<f32> = (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::<f32>().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<FaceTemplate> {
(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::<f32>().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<u8> = (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<u8> = (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);