use std::fmt::Debug;
#[derive(Default)]
pub struct ReplayWindow {
last: u64,
blocks: [u64; ReplayWindow::N_BLOCKS as usize],
}
impl Debug for ReplayWindow {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
struct BlockFormatter<'b>(&'b [u64]);
impl<'b> Debug for BlockFormatter<'b> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for b in self.0 {
write!(f, "{:08b} ", b.reverse_bits())?;
}
Ok(())
}
}
f.debug_struct("ReplayWindow")
.field("last", &self.last)
.field("bits", &BlockFormatter(&self.blocks))
.finish()
}
}
impl ReplayWindow {
const TOTAL_BITS: u64 = 256;
const N_BLOCKS: u64 = Self::TOTAL_BITS / u64::BITS as u64;
const BIT_IDX_BITMASK: u64 = (u64::BITS - 1) as u64;
const BIT_IDX_SHIFT: u32 = u64::BITS.ilog2();
const BLOCK_IDX_BITMASK: u64 = Self::N_BLOCKS - 1;
pub const WINDOW_SIZE: u64 = (Self::N_BLOCKS - 1) * u64::BITS as u64;
fn smallest_valid(&self) -> u64 {
self.last.saturating_sub(Self::WINDOW_SIZE - 1)
}
fn block_idx_unbounded(&self, counter: u64) -> u64 {
counter >> Self::BIT_IDX_SHIFT
}
fn bit_idx(&self, counter: u64) -> u64 {
counter & Self::BIT_IDX_BITMASK
}
fn block_idx_and_bit_mask(&self, counter: u64) -> (usize, u64) {
let block_idx = self.block_idx_unbounded(counter) & Self::BLOCK_IDX_BITMASK;
(block_idx as usize, 1 << self.bit_idx(counter))
}
pub fn check(&self, counter: u64) -> bool {
if counter > self.last {
return true;
}
if counter < self.smallest_valid() {
return false;
}
let (block_idx, bit_mask) = self.block_idx_and_bit_mask(counter);
self.blocks[block_idx] & bit_mask == 0
}
pub fn set(&mut self, counter: u64) {
if counter < self.smallest_valid() {
panic!(
"invalid set: counter {} is older than smallest valid {}",
counter,
self.smallest_valid()
);
}
if counter > self.last {
let cur_block = self.block_idx_unbounded(self.last);
let new_block = self.block_idx_unbounded(counter);
let delta = new_block - cur_block;
if delta >= Self::N_BLOCKS {
self.blocks = [0; Self::N_BLOCKS as usize];
} else {
for i in cur_block..new_block {
let idx = (i + 1) & Self::BLOCK_IDX_BITMASK;
self.blocks[idx as usize] = 0;
}
}
self.last = counter;
}
let (block_idx, bit_mask) = self.block_idx_and_bit_mask(counter);
if self.blocks[block_idx] & bit_mask != 0 {
panic!(
"invalid set: counter {} was already set previously",
counter
);
}
self.blocks[block_idx] |= bit_mask;
}
#[cfg(test)]
fn check_and_set(&mut self, counter: u64) -> bool {
let accept = self.check(counter);
if accept {
self.set(counter);
}
accept
}
#[cfg(test)]
fn received_in_window(&self) -> u64 {
let counters = self.smallest_valid()..self.last + 1;
counters
.map(|ctr| if self.check(ctr) { 0 } else { 1 })
.sum()
}
}
#[cfg(test)]
mod tests {
use std::{cmp::max, collections::HashSet};
use super::*;
#[test]
fn just_advance() {
let mut window = ReplayWindow::default();
for counter in 0..600 {
assert!(window.check_and_set(counter));
assert_eq!(
window.received_in_window(),
(counter + 1).clamp(0, ReplayWindow::WINDOW_SIZE)
);
}
}
#[test]
fn out_of_order() {
let mut window = ReplayWindow::default();
assert!(window.check_and_set(500));
assert!(!window.check(500));
assert!(!window.check(100));
assert_eq!(window.received_in_window(), 1);
for (i, counter) in (400..450).rev().enumerate() {
assert!(window.check_and_set(counter));
assert_eq!(window.received_in_window(), (i + 2) as u64);
}
for (i, counter) in (451..500).enumerate() {
assert!(window.check_and_set(counter));
assert_eq!(window.received_in_window(), (i + 52) as u64);
}
}
proptest::proptest! {
#[test]
fn any_order(counters in proptest::collection::vec(0u64..1000, 0..2000)) {
let mut seen = HashSet::new();
let mut latest = None;
let mut window = ReplayWindow::default();
for counter in counters {
let accepted = window.check_and_set(counter);
if accepted {
assert!(!seen.contains(&counter));
if let Some(latest_ctr) = latest {
assert!(counter >= window.smallest_valid());
latest = Some(max(latest_ctr, counter))
} else {
latest = Some(counter);
}
seen.insert(counter);
} else {
assert!(seen.contains(&counter) || counter < window.smallest_valid());
}
}
}
}
}