#[cfg(feature = "alloc")]
use alloc::string::ToString;
use subtle::ConstantTimeEq;
use crate::error::Result;
#[cfg(feature = "alloc")]
#[derive(Clone)]
pub struct TimingValidator {
enable_timing_validation: bool,
}
#[cfg(feature = "alloc")]
impl TimingValidator {
pub fn new() -> Result<Self> {
Ok(Self {
enable_timing_validation: true,
})
}
pub fn constant_time_compare(&self, a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
a.ct_eq(b).into()
}
pub fn constant_time_select<T: Copy>(&self, choice: bool, a: T, b: T) -> T {
if choice { a } else { b }
}
pub fn constant_time_assign<T: Copy>(&self, choice: bool, dst: &mut T, src: T) {
*dst = self.constant_time_select(choice, src, *dst);
}
pub fn constant_time_copy(&self, choice: bool, dst: &mut [u8], src: &[u8]) {
assert_eq!(dst.len(), src.len(), "Slices must have the same length");
for (d, s) in dst.iter_mut().zip(src.iter()) {
*d = self.constant_time_select(choice, *s, *d);
}
}
pub fn validate_timing_safety(&self, operation: &str) -> Result<()> {
if !self.enable_timing_validation {
return Ok(());
}
if operation.is_empty() {
return Err(crate::error::Error::InvalidState {
operation: "timing_validation".to_string(),
reason: "Operation name cannot be empty".to_string(),
});
}
Ok(())
}
pub fn set_timing_validation(&mut self, enabled: bool) {
self.enable_timing_validation = enabled;
}
pub fn is_timing_validation_enabled(&self) -> bool {
self.enable_timing_validation
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timing_validator_creation() {
let validator = TimingValidator::new();
assert!(
validator.is_ok(),
"TimingValidator should be created successfully"
);
}
#[test]
fn test_constant_time_compare() {
let validator = TimingValidator::new().unwrap();
let a = vec![1, 2, 3, 4];
let b = vec![1, 2, 3, 4];
assert!(
validator.constant_time_compare(&a, &b),
"Should return true for equal slices"
);
let c = vec![1, 2, 3, 5];
assert!(
!validator.constant_time_compare(&a, &c),
"Should return false for different slices"
);
let d = vec![1, 2, 3];
assert!(
!validator.constant_time_compare(&a, &d),
"Should return false for different length slices"
);
}
#[test]
fn test_constant_time_select() {
let validator = TimingValidator::new().unwrap();
let result = validator.constant_time_select(true, 42, 24);
assert_eq!(result, 42, "Should select first value when choice is true");
let result = validator.constant_time_select(false, 42, 24);
assert_eq!(
result, 24,
"Should select second value when choice is false"
);
}
#[test]
fn test_constant_time_assign() {
let validator = TimingValidator::new().unwrap();
let mut value = 10;
validator.constant_time_assign(true, &mut value, 20);
assert_eq!(value, 20, "Should assign new value when choice is true");
validator.constant_time_assign(false, &mut value, 30);
assert_eq!(value, 20, "Should not change value when choice is false");
}
#[test]
fn test_constant_time_copy() {
let validator = TimingValidator::new().unwrap();
let mut dst = vec![1, 2, 3, 4];
let src = vec![5, 6, 7, 8];
validator.constant_time_copy(true, &mut dst, &src);
assert_eq!(dst, src, "Should copy source when choice is true");
let original = dst.clone();
validator.constant_time_copy(false, &mut dst, &[9, 10, 11, 12]);
assert_eq!(
dst, original,
"Should not change destination when choice is false"
);
}
#[test]
fn test_validate_timing_safety() {
let validator = TimingValidator::new().unwrap();
let result = validator.validate_timing_safety("test_operation");
assert!(result.is_ok(), "Should accept valid operation name");
let result = validator.validate_timing_safety("");
assert!(result.is_err(), "Should reject empty operation name");
}
#[test]
fn test_timing_validation_control() {
let mut validator = TimingValidator::new().unwrap();
assert!(
validator.is_timing_validation_enabled(),
"Timing validation should be enabled by default"
);
validator.set_timing_validation(false);
assert!(
!validator.is_timing_validation_enabled(),
"Timing validation should be disabled"
);
validator.set_timing_validation(true);
assert!(
validator.is_timing_validation_enabled(),
"Timing validation should be enabled"
);
}
}