const WINDOW_SIZE: usize = 2048;
#[derive(Debug, Clone)]
pub struct ReplayWindow {
highest_counter: u64,
bitmap: [u64; WINDOW_SIZE / 64],
initialized: bool,
}
impl ReplayWindow {
pub fn new() -> Self {
Self {
highest_counter: 0,
bitmap: [0; WINDOW_SIZE / 64],
initialized: false,
}
}
pub fn check_and_update(&mut self, counter: u64) -> bool {
if !self.initialized {
self.highest_counter = counter;
self.initialized = true;
return true;
}
if counter > self.highest_counter {
let delta = counter - self.highest_counter;
if delta >= WINDOW_SIZE as u64 {
self.bitmap = [0; WINDOW_SIZE / 64];
} else {
self.shift_bitmap_and_mark(delta as usize);
}
self.highest_counter = counter;
return true;
}
if counter == self.highest_counter {
return false;
}
let delta = self.highest_counter - counter;
if delta > WINDOW_SIZE as u64 {
return false;
}
let bit_pos = (delta - 1) as usize;
let block_idx = bit_pos / 64;
let bit_idx = bit_pos % 64;
if self.bitmap[block_idx] & (1u64 << bit_idx) != 0 {
return false;
}
self.bitmap[block_idx] |= 1u64 << bit_idx;
true
}
fn shift_bitmap_and_mark(&mut self, delta: usize) {
if delta == 0 {
return;
}
if delta >= WINDOW_SIZE {
self.bitmap = [0; WINDOW_SIZE / 64];
self.bitmap[0] |= 1;
return;
}
let block_shift = delta / 64;
let bit_shift = delta % 64;
if bit_shift == 0 {
for i in (block_shift..self.bitmap.len()).rev() {
self.bitmap[i] = self.bitmap[i - block_shift];
}
for i in 0..block_shift {
self.bitmap[i] = 0;
}
} else {
for i in (block_shift + 1..self.bitmap.len()).rev() {
self.bitmap[i] = (self.bitmap[i - block_shift] << bit_shift)
| (self.bitmap[i - block_shift - 1] >> (64 - bit_shift));
}
if block_shift < self.bitmap.len() {
self.bitmap[block_shift] = self.bitmap[0] << bit_shift;
}
for i in 0..block_shift {
self.bitmap[i] = 0;
}
}
let mark_pos = delta - 1;
let block_idx = mark_pos / 64;
let bit_idx = mark_pos % 64;
self.bitmap[block_idx] |= 1u64 << bit_idx;
}
pub fn highest_counter(&self) -> u64 {
self.highest_counter
}
}
impl Default for ReplayWindow {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_window() {
let window = ReplayWindow::new();
assert_eq!(window.highest_counter(), 0);
}
#[test]
fn test_accept_zero_counter() {
let mut window = ReplayWindow::new();
assert!(window.check_and_update(0));
assert_eq!(window.highest_counter(), 0);
assert!(!window.check_and_update(0));
}
#[test]
fn test_sequential_counters() {
let mut window = ReplayWindow::new();
assert!(window.check_and_update(1));
assert_eq!(window.highest_counter(), 1);
assert!(window.check_and_update(2));
assert_eq!(window.highest_counter(), 2);
assert!(window.check_and_update(3));
assert_eq!(window.highest_counter(), 3);
}
#[test]
fn test_reject_replay() {
let mut window = ReplayWindow::new();
assert!(window.check_and_update(1));
assert!(window.check_and_update(2));
assert!(window.check_and_update(3));
assert!(!window.check_and_update(2));
assert!(!window.check_and_update(1));
assert!(!window.check_and_update(3));
}
#[test]
fn test_out_of_order_within_window() {
let mut window = ReplayWindow::new();
assert!(window.check_and_update(10));
assert_eq!(window.highest_counter(), 10);
assert!(window.check_and_update(5));
assert!(window.check_and_update(8));
assert!(window.check_and_update(3));
assert!(!window.check_and_update(5));
assert!(!window.check_and_update(8));
assert!(!window.check_and_update(10));
}
#[test]
fn test_counter_too_old() {
let mut window = ReplayWindow::new();
assert!(window.check_and_update(3000));
assert!(!window.check_and_update(500));
}
#[test]
fn test_large_jump() {
let mut window = ReplayWindow::new();
assert!(window.check_and_update(100));
assert!(window.check_and_update(100 + WINDOW_SIZE as u64 + 100));
assert!(!window.check_and_update(100));
}
#[test]
fn test_window_edges() {
let mut window = ReplayWindow::new();
assert!(window.check_and_update(WINDOW_SIZE as u64));
assert!(window.check_and_update(1));
assert!(window.check_and_update(WINDOW_SIZE as u64 + 1));
assert!(!window.check_and_update(1));
assert!(window.check_and_update(2));
}
#[test]
fn test_full_window() {
let mut window = ReplayWindow::new();
assert!(window.check_and_update(WINDOW_SIZE as u64));
for i in 1..WINDOW_SIZE as u64 {
assert!(window.check_and_update(i));
}
for i in 1..=WINDOW_SIZE as u64 {
assert!(!window.check_and_update(i));
}
}
#[test]
fn test_wrapping_behavior() {
let mut window = ReplayWindow::new();
let start = u64::MAX - 100;
assert!(window.check_and_update(start));
assert!(window.check_and_update(start + 1));
assert!(window.check_and_update(start + 2));
assert!(!window.check_and_update(start));
assert!(!window.check_and_update(start + 1));
}
}