crate::ix!();
pub fn timing_resistant_equal<T>(a: &T, b: &T) -> bool
where
T: AsRef<[u8]> + ?Sized,
{
let a_bytes = a.as_ref();
let b_bytes = b.as_ref();
if b_bytes.is_empty() {
trace!(
target: "timing_resistant_equal",
a_len = a_bytes.len(),
b_len = 0,
"b is empty – result = {}",
a_bytes.is_empty()
);
return a_bytes.is_empty();
}
let mut acc: usize = a_bytes.len() ^ b_bytes.len();
for (i, &byte_a) in a_bytes.iter().enumerate() {
let byte_b = b_bytes[i % b_bytes.len()];
acc |= (byte_a ^ byte_b) as usize;
}
let result = acc == 0;
debug!(
target: "timing_resistant_equal",
a_len = a_bytes.len(),
b_len = b_bytes.len(),
equal = result,
"comparison complete"
);
result
}
#[cfg(test)]
mod timing_and_integral_tests {
use super::*;
#[traced_test]
fn equal_byte_slices_match() {
let lhs = b"bitcoin";
let rhs = b"bitcoin";
assert!(timing_resistant_equal(&lhs[..], &rhs[..]));
}
#[traced_test]
fn differing_byte_slices_do_not_match() {
let lhs = b"bitcoin";
let rhs = b"bitco1n";
assert!(!timing_resistant_equal(&lhs[..], &rhs[..]));
}
#[traced_test]
fn unequal_lengths_do_not_match() {
let lhs = b"abcde";
let rhs = b"abc";
assert!(!timing_resistant_equal(&lhs[..], &rhs[..]));
}
#[traced_test]
fn empty_vs_empty_match() {
let empty: &[u8] = b"";
assert!(timing_resistant_equal(&empty, &empty));
}
#[traced_test]
fn nonempty_vs_empty_do_not_match() {
let lhs = b"non-empty";
let rhs: &[u8] = b"";
assert!(!timing_resistant_equal(&lhs[..], &rhs));
}
#[traced_test]
fn signed_positive_parses() {
assert_eq!(to_integral::<i32>("42"), Some(42));
}
#[traced_test]
fn signed_negative_parses() {
assert_eq!(to_integral::<i32>("-42"), Some(-42));
}
#[traced_test]
fn unsigned_negative_rejected() {
assert_eq!(to_integral::<u32>("-1"), None);
}
#[traced_test]
fn leading_plus_rejected() {
assert_eq!(to_integral::<i64>("+5"), None);
}
#[traced_test]
fn leading_whitespace_rejected() {
assert_eq!(to_integral::<i32>(" 7"), None);
}
#[traced_test]
fn trailing_whitespace_rejected() {
assert_eq!(to_integral::<i32>("7 "), None);
}
#[traced_test]
fn trailing_garbage_rejected() {
assert_eq!(to_integral::<i32>("99bottles"), None);
}
#[traced_test]
fn out_of_range_rejected() {
let gigantic = "9223372036854775808";
assert_eq!(to_integral::<i64>(gigantic), None);
}
}