pub struct ReplayWindow {
base: u64,
window: [u64; 16],
initialized: bool,
}
const WINDOW_SIZE: u64 = 1024;
impl ReplayWindow {
pub fn new() -> Self {
Self {
base: 0,
window: [0u64; 16],
initialized: false,
}
}
pub fn check_and_mark(&mut self, ctr: u64) -> Result<(), ()> {
if !self.initialized {
self.base = ctr;
self.window[0] = 1;
self.initialized = true;
return Ok(());
}
if ctr > self.base {
let advance = ctr - self.base;
shift_window_up(&mut self.window, advance as usize);
self.base = ctr;
self.window[0] |= 1;
Ok(())
} else {
let behind = self.base - ctr;
if behind >= WINDOW_SIZE {
return Err(()); }
let word = (behind / 64) as usize;
let bit = behind % 64;
if self.window[word] & (1u64 << bit) != 0 {
return Err(()); }
self.window[word] |= 1u64 << bit;
Ok(())
}
}
pub fn reset(&mut self) {
self.initialized = false;
self.window = [0u64; 16];
}
}
impl Default for ReplayWindow {
fn default() -> Self {
Self::new()
}
}
fn shift_window_up(window: &mut [u64; 16], by: usize) {
if by >= WINDOW_SIZE as usize {
*window = [0; 16];
return;
}
let word_shift = by / 64;
let bit_shift = by % 64;
for i in (0..16).rev() {
if i < word_shift {
window[i] = 0;
continue;
}
let src = i - word_shift;
if bit_shift == 0 {
window[i] = window[src];
} else {
let lo = window[src];
let hi = if src > 0 { window[src - 1] } else { 0 };
window[i] = (lo << bit_shift) | (hi >> (64 - bit_shift));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fresh_window_accepts_first() {
let mut w = ReplayWindow::new();
assert!(w.check_and_mark(0).is_ok());
}
#[test]
fn replay_is_rejected() {
let mut w = ReplayWindow::new();
w.check_and_mark(10).unwrap();
assert!(w.check_and_mark(10).is_err());
}
#[test]
fn in_order_sequence() {
let mut w = ReplayWindow::new();
for i in 0..100u64 {
assert!(w.check_and_mark(i).is_ok(), "ctr={i} should be accepted");
}
}
#[test]
fn out_of_order_within_window() {
let mut w = ReplayWindow::new();
w.check_and_mark(0).unwrap();
w.check_and_mark(1023).unwrap();
assert!(
w.check_and_mark(0).is_err(),
"0 should be detected as replay"
);
assert!(w.check_and_mark(500).is_ok());
}
#[test]
fn too_old_is_rejected() {
let mut w = ReplayWindow::new();
w.check_and_mark(2000).unwrap();
assert!(w.check_and_mark(0).is_err(), "0 is 2000 behind and too old");
}
#[test]
fn window_advance_across_word_boundary() {
let mut w = ReplayWindow::new();
w.check_and_mark(63).unwrap();
w.check_and_mark(127).unwrap(); assert!(w.check_and_mark(63).is_err());
assert!(w.check_and_mark(127).is_err());
assert!(w.check_and_mark(100).is_ok());
}
}