use crate::header;
use std::collections::HashMap;
use super::{
FrameValidation, ReplayAttackProtection, ReplayAttackProtectionError, ReplayToken, Tolerance,
UnvalidatedFrame,
};
pub struct ReplayAttackProtectionStore {
validators: HashMap<header::KeyId, ReplayAttackProtection>,
tolerance: Tolerance,
}
impl ReplayAttackProtectionStore {
pub fn new(tolerance: Tolerance) -> Self {
ReplayAttackProtectionStore {
validators: HashMap::new(),
tolerance,
}
}
pub fn remove<K>(&mut self, key_id: K) -> bool
where
K: Into<header::KeyId>,
{
self.validators.remove(&key_id.into()).is_some()
}
pub fn retain<F>(&mut self, mut keep: F)
where
F: FnMut(header::KeyId) -> bool,
{
self.validators.retain(|key_id, _| keep(*key_id));
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ReplayStoreToken(Screened);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Screened {
Tracked(ReplayToken),
Untracked {
key_id: header::KeyId,
counter: header::Counter,
},
}
impl FrameValidation for ReplayAttackProtectionStore {
type Token = ReplayStoreToken;
type Error = ReplayAttackProtectionError;
fn screen(&self, unvalidated: UnvalidatedFrame<'_>) -> Result<Self::Token, Self::Error> {
let header = unvalidated.header();
let screened = match self.validators.get(&header.key_id()) {
Some(validator) => Screened::Tracked(validator.screen(unvalidated)?),
None => Screened::Untracked {
key_id: header.key_id(),
counter: header.counter(),
},
};
Ok(ReplayStoreToken(screened))
}
fn record(&mut self, ReplayStoreToken(screened): Self::Token) {
let tolerance = self.tolerance;
let (key_id, token) = match screened {
Screened::Tracked(token) => (token.key_id, token),
Screened::Untracked { key_id, counter } => (key_id, ReplayToken { key_id, counter }),
};
self.validators
.entry(key_id)
.or_insert_with(|| ReplayAttackProtection::new(key_id, tolerance))
.record(token);
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::frame::validation::util::test::{screen, screen_and_record};
use ReplayAttackProtectionError::DuplicatedFrame;
const TOLERANCE: usize = 128;
const KID_A: header::KeyId = 1;
const KID_B: header::KeyId = 2;
const COUNTER: header::Counter = 2480;
fn store() -> ReplayAttackProtectionStore {
ReplayAttackProtectionStore::new(Tolerance::new(TOLERANCE))
}
#[test]
fn tracks_key_ids_independently() {
let mut store = store();
assert!(screen_and_record(&mut store, KID_A, COUNTER).is_ok());
assert!(screen_and_record(&mut store, KID_B, COUNTER).is_ok());
}
#[test]
fn reuses_the_window_of_a_key_id() {
let mut store = store();
assert!(screen_and_record(&mut store, KID_A, COUNTER).is_ok());
assert!(matches!(
screen(&store, KID_A, COUNTER),
Err(DuplicatedFrame { .. })
));
}
#[test]
fn screens_a_known_key_id() {
let mut store = store();
screen_and_record(&mut store, KID_A, COUNTER).unwrap();
assert!(matches!(
screen(&store, KID_A, COUNTER),
Err(DuplicatedFrame { .. })
));
}
#[test]
fn screening_accepts_an_unknown_key_id() {
let store = store();
assert!(screen(&store, KID_A, COUNTER).is_ok());
}
#[test]
fn screening_does_not_track_a_key_id() {
let store = store();
screen(&store, KID_A, COUNTER).unwrap();
assert!(store.validators.is_empty());
}
#[test]
fn removes_a_tracked_key_id() {
let mut store = store();
screen_and_record(&mut store, KID_A, COUNTER).unwrap();
assert!(store.remove(KID_A));
assert!(screen(&store, KID_A, COUNTER).is_ok());
}
#[test]
fn retains_only_the_matching_key_ids() {
let mut store = store();
screen_and_record(&mut store, KID_A, COUNTER).unwrap();
screen_and_record(&mut store, KID_B, COUNTER).unwrap();
store.retain(|key_id| key_id == KID_B);
assert!(screen(&store, KID_A, COUNTER).is_ok());
assert!(matches!(
screen(&store, KID_B, COUNTER),
Err(DuplicatedFrame { .. })
));
}
#[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();
screen_and_record(&mut store, KID_A, COUNTER).unwrap();
screen_and_record(&mut store, KID_B, COUNTER).unwrap();
store.remove(KID_A);
assert!(matches!(
screen(&store, KID_B, COUNTER),
Err(DuplicatedFrame { .. })
));
}
}