const WINDOW_SIZE: u64 = 1024;
const BITMAP_WORDS: usize = (WINDOW_SIZE / 64) as usize;
pub struct ReplayWindow {
top: u64,
bitmap: [u64; BITMAP_WORDS],
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ReplayState {
pub top: u64,
pub bitmap_words: Vec<u64>,
}
impl ReplayWindow {
pub fn new() -> Self {
Self {
top: 0,
bitmap: [0u64; BITMAP_WORDS],
}
}
pub fn check(&self, counter: u64) -> bool {
if counter == 0 {
return false;
}
if self.top == 0 {
return true;
}
if counter > self.top {
return true;
}
let diff = self.top - counter;
if diff >= WINDOW_SIZE {
return false;
}
let word_idx = (diff as usize) / 64;
let bit_idx = (diff as usize) % 64;
(self.bitmap[word_idx] >> bit_idx) & 1 == 0
}
pub fn accept(&mut self, counter: u64) {
if counter == 0 {
return;
}
if self.top == 0 || counter > self.top {
let shift = if self.top == 0 { 0 } else { counter - self.top };
self.shift_window(shift);
self.top = counter;
self.bitmap[0] |= 1;
} else {
let diff = self.top - counter;
if diff < WINDOW_SIZE {
let word_idx = (diff as usize) / 64;
let bit_idx = (diff as usize) % 64;
self.bitmap[word_idx] |= 1 << bit_idx;
}
}
}
pub fn check_and_accept(&mut self, counter: u64) -> bool {
if !self.check(counter) {
return false;
}
self.accept(counter);
true
}
pub fn top(&self) -> u64 {
self.top
}
pub fn window_size(&self) -> u64 {
WINDOW_SIZE
}
pub fn snapshot(&self) -> ReplayState {
ReplayState {
top: self.top,
bitmap_words: self.bitmap.to_vec(),
}
}
pub fn restore(&mut self, state: &ReplayState) -> bool {
if state.bitmap_words.len() != BITMAP_WORDS {
return false;
}
self.top = state.top;
self.bitmap.copy_from_slice(&state.bitmap_words);
true
}
fn shift_window(&mut self, shift: u64) {
if shift == 0 {
return;
}
if shift >= WINDOW_SIZE {
self.bitmap = [0u64; BITMAP_WORDS];
return;
}
let word_shift = (shift / 64) as usize;
let bit_shift = (shift % 64) as u32;
if word_shift > 0 {
for i in (word_shift..BITMAP_WORDS).rev() {
self.bitmap[i] = self.bitmap[i - word_shift];
}
for i in 0..word_shift.min(BITMAP_WORDS) {
self.bitmap[i] = 0;
}
}
if bit_shift > 0 {
for i in (1..BITMAP_WORDS).rev() {
self.bitmap[i] =
(self.bitmap[i] << bit_shift) | (self.bitmap[i - 1] >> (64 - bit_shift));
}
self.bitmap[0] <<= bit_shift;
}
}
}
impl Default for ReplayWindow {
fn default() -> Self {
Self::new()
}
}
pub fn merge_replay_states(a: &ReplayState, b: &ReplayState) -> Option<ReplayState> {
if a.bitmap_words.len() != BITMAP_WORDS || b.bitmap_words.len() != BITMAP_WORDS {
return None;
}
let top = a.top.max(b.top);
if top == 0 {
return Some(ReplayState {
top: 0,
bitmap_words: vec![0u64; BITMAP_WORDS],
});
}
let mut merged = ReplayWindow::new();
merged.top = top;
let left = top.saturating_sub(WINDOW_SIZE - 1);
let mut counter = left;
while counter <= top {
if state_contains(a, counter) || state_contains(b, counter) {
let diff = top - counter;
let word_idx = (diff as usize) / 64;
let bit_idx = (diff as usize) % 64;
merged.bitmap[word_idx] |= 1 << bit_idx;
}
if counter == u64::MAX {
break;
}
counter += 1;
}
Some(merged.snapshot())
}
fn state_contains(state: &ReplayState, counter: u64) -> bool {
if counter == 0 || state.top == 0 || counter > state.top {
return false;
}
let diff = state.top - counter;
if diff >= WINDOW_SIZE {
return false;
}
let word_idx = (diff as usize) / 64;
let bit_idx = (diff as usize) % 64;
(state.bitmap_words[word_idx] >> bit_idx) & 1 == 1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fresh_window_accepts_first_packet() {
let mut w = ReplayWindow::new();
assert!(w.check_and_accept(1));
assert_eq!(w.top(), 1);
}
#[test]
fn rejects_counter_zero() {
let w = ReplayWindow::new();
assert!(!w.check(0));
}
#[test]
fn rejects_duplicate() {
let mut w = ReplayWindow::new();
assert!(w.check_and_accept(5));
assert!(!w.check_and_accept(5));
}
#[test]
fn accepts_sequential_counters() {
let mut w = ReplayWindow::new();
for i in 1..=100 {
assert!(w.check_and_accept(i), "should accept {i}");
}
assert_eq!(w.top(), 100);
}
#[test]
fn accepts_out_of_order_within_window() {
let mut w = ReplayWindow::new();
w.check_and_accept(10);
w.check_and_accept(8);
w.check_and_accept(12);
w.check_and_accept(9);
assert!(!w.check(8));
assert!(!w.check(9));
assert!(!w.check(10));
assert!(!w.check(12));
assert!(w.check(11));
}
#[test]
fn rejects_too_old() {
let mut w = ReplayWindow::new();
w.check_and_accept(WINDOW_SIZE + 100);
assert!(!w.check(1));
assert!(!w.check(100));
}
#[test]
fn window_edge_accepted() {
let mut w = ReplayWindow::new();
w.check_and_accept(WINDOW_SIZE);
assert!(w.check_and_accept(1));
}
#[test]
fn large_jump_clears_window() {
let mut w = ReplayWindow::new();
for i in 1..=50 {
w.check_and_accept(i);
}
w.check_and_accept(WINDOW_SIZE + 200);
assert!(!w.check(50));
assert!(!w.check(1));
}
#[test]
fn sequential_high_volume() {
let mut w = ReplayWindow::new();
for i in 1..=5000 {
assert!(w.check_and_accept(i), "should accept {i}");
}
assert!(!w.check(5000));
assert!(!w.check(4999));
assert!(!w.check(1));
assert!(w.check_and_accept(5001));
}
#[test]
fn check_without_accept_does_not_mark() {
let mut w = ReplayWindow::new();
w.check_and_accept(10);
assert!(w.check(11)); assert!(w.check(11)); }
#[test]
fn window_size_is_1024() {
let w = ReplayWindow::new();
assert_eq!(w.window_size(), 1024);
}
#[test]
fn interleaved_gaps_handled() {
let mut w = ReplayWindow::new();
for i in (2..=200).step_by(2) {
assert!(w.check_and_accept(i));
}
assert!(w.check_and_accept(199));
assert!(w.check_and_accept(197));
assert!(!w.check(200));
assert!(!w.check(198));
}
#[test]
fn snapshot_restore_roundtrip() {
let mut w = ReplayWindow::new();
for i in [10, 11, 20, 18, 19] {
assert!(w.check_and_accept(i));
}
let snap = w.snapshot();
let mut restored = ReplayWindow::new();
assert!(restored.restore(&snap));
assert_eq!(restored.top(), w.top());
assert_eq!(restored.snapshot(), snap);
assert!(!restored.check(20));
assert!(!restored.check(19));
assert!(restored.check_and_accept(21));
}
#[test]
fn restore_rejects_invalid_bitmap_size() {
let mut w = ReplayWindow::new();
let bad = ReplayState {
top: 42,
bitmap_words: vec![0; 3],
};
assert!(!w.restore(&bad));
}
#[test]
fn merge_replay_states_keeps_union_within_window() {
let mut w1 = ReplayWindow::new();
let mut w2 = ReplayWindow::new();
for c in [10, 11, 13, 21] {
w1.check_and_accept(c);
}
for c in [12, 20, 21, 30] {
w2.check_and_accept(c);
}
let merged = merge_replay_states(&w1.snapshot(), &w2.snapshot()).expect("merge");
let mut rw = ReplayWindow::new();
assert!(rw.restore(&merged));
for seen in [10, 11, 12, 13, 20, 21, 30] {
assert!(!rw.check(seen), "merged snapshot must contain {seen}");
}
assert!(rw.check(31), "next unseen packet must be acceptable");
}
}