use crate::{
error::Result,
frame::{FrameValidation, ReplayAttackProtection},
header::{self, SframeHeader},
};
use std::{cell::RefCell, collections::HashMap};
pub struct ReplayAttackProtectionStore {
validators: RefCell<HashMap<header::KeyId, ReplayAttackProtection>>,
tolerance: u64,
}
impl ReplayAttackProtectionStore {
pub fn with_tolerance(tolerance: u64) -> Self {
assert!(tolerance > 0, "Tolerance must be greater than 0");
let _: usize = tolerance
.try_into()
.expect("Tolerance exceeds OS capabilities");
ReplayAttackProtectionStore {
validators: RefCell::new(HashMap::new()),
tolerance,
}
}
pub fn remove<K>(&mut self, key_id: K) -> bool
where
K: Into<header::KeyId>,
{
self.validators.get_mut().remove(&key_id.into()).is_some()
}
pub fn retain<F>(&mut self, mut keep: F)
where
F: FnMut(header::KeyId) -> bool,
{
self.validators.get_mut().retain(|key_id, _| keep(*key_id));
}
pub fn inspect(&self, header: &SframeHeader) -> Result<()> {
match self.validators.borrow().get(&header.key_id()) {
Some(validator) => validator.inspect(header),
None => Ok(()),
}
}
}
impl FrameValidation for ReplayAttackProtectionStore {
fn validate(&self, header: &SframeHeader) -> Result<()> {
let key_id = header.key_id();
self.validators
.borrow_mut()
.entry(key_id)
.or_insert_with(|| {
ReplayAttackProtection::with_tolerance(self.tolerance).for_key_id(key_id)
})
.validate(header)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{frame::FrameValidation, header::SframeHeader};
const TOLERANCE: u64 = 128;
const KID_A: u64 = 1;
const KID_B: u64 = 2;
const COUNTER: u64 = 2480;
fn store() -> ReplayAttackProtectionStore {
ReplayAttackProtectionStore::with_tolerance(TOLERANCE)
}
fn header(key_id: u64, counter: u64) -> SframeHeader {
SframeHeader::new(key_id, counter)
}
#[test]
fn tracks_key_ids_independently() {
let store = store();
assert!(store.validate(&header(KID_A, COUNTER)).is_ok());
assert!(store.validate(&header(KID_B, COUNTER)).is_ok());
}
#[test]
fn reuses_the_window_of_a_key_id() {
let store = store();
assert!(store.validate(&header(KID_A, COUNTER)).is_ok());
assert!(store.validate(&header(KID_A, COUNTER)).is_err());
}
#[test]
fn inspect_screens_a_known_key_id() {
let store = store();
store.validate(&header(KID_A, COUNTER)).unwrap();
assert!(store.inspect(&header(KID_A, COUNTER)).is_err());
}
#[test]
fn inspect_accepts_an_unknown_key_id() {
let store = store();
assert!(store.inspect(&header(KID_A, COUNTER)).is_ok());
}
#[test]
fn inspect_does_not_track_a_key_id() {
let store = store();
store.inspect(&header(KID_A, COUNTER)).unwrap();
assert!(store.validate(&header(KID_A, COUNTER)).is_ok());
}
#[test]
fn removes_a_tracked_key_id() {
let mut store = store();
store.validate(&header(KID_A, COUNTER)).unwrap();
assert!(store.remove(KID_A));
assert!(store.validate(&header(KID_A, COUNTER)).is_ok());
}
#[test]
fn retains_only_the_matching_key_ids() {
let mut store = store();
store.validate(&header(KID_A, COUNTER)).unwrap();
store.validate(&header(KID_B, COUNTER)).unwrap();
store.retain(|key_id| key_id == KID_B);
assert!(store.validate(&header(KID_A, COUNTER)).is_ok());
assert!(store.validate(&header(KID_B, COUNTER)).is_err());
}
#[test]
fn reports_removal_of_an_untracked_key_id() {
let mut store = store();
assert!(!store.remove(KID_A));
}
#[test]
fn keeps_other_key_ids_on_removal() {
let mut store = store();
store.validate(&header(KID_A, COUNTER)).unwrap();
store.validate(&header(KID_B, COUNTER)).unwrap();
store.remove(KID_A);
assert!(store.inspect(&header(KID_B, COUNTER)).is_err());
}
}