use std::time::Duration;
use hmac::{Hmac, Mac};
use sha2::Sha256;
pub fn exponential_backoff(retry_count: u32, base_delay: Duration) -> Duration {
let base_ms = base_delay.as_millis() as f64;
let exponential = base_ms * 2_f64.powi(retry_count as i32);
let jitter = rand_jitter(exponential * 0.3);
let total = (exponential + jitter) as u64;
Duration::from_millis(total.min(30_000))
}
fn rand_jitter(max: f64) -> f64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::time::SystemTime;
let mut hasher = DefaultHasher::new();
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos()
.hash(&mut hasher);
let hash = hasher.finish();
(hash as f64 / u64::MAX as f64) * max
}
pub fn validate_email(email: &str) -> bool {
let parts: Vec<&str> = email.splitn(2, '@').collect();
if parts.len() != 2 || parts[0].is_empty() {
return false;
}
let domain_parts: Vec<&str> = parts[1].splitn(2, '.').collect();
domain_parts.len() == 2 && !domain_parts[0].is_empty() && !domain_parts[1].is_empty()
}
pub fn verify_webhook_signature(
payload: &str,
signature: &str,
timestamp: &str,
secret: &str,
tolerance_seconds: Option<i64>,
) -> bool {
let tolerance = tolerance_seconds.unwrap_or(300);
let now = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64;
let payload_ts: i64 = match timestamp.parse() {
Ok(t) => t,
Err(_) => return false,
};
if (now - payload_ts).abs() > tolerance {
return false;
}
let signed_payload = format!("{}.{}", timestamp, payload);
let mut mac = match Hmac::<Sha256>::new_from_slice(secret.as_bytes()) {
Ok(m) => m,
Err(_) => return false,
};
mac.update(signed_payload.as_bytes());
let expected = hex::encode(mac.finalize().into_bytes());
constant_time_eq(signature.as_bytes(), expected.as_bytes())
}
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
a.iter().zip(b.iter()).fold(0u8, |acc, (x, y)| acc | (x ^ y)) == 0
}