#[derive(Debug, Clone, Copy, Default)]
struct SeenEntry {
message_id: u32,
seen_at: u32,
valid: bool,
}
pub struct SeenCache<const N: usize> {
entries: [SeenEntry; N],
next: usize,
tick: u32,
lifetime: u32,
}
impl<const N: usize> SeenCache<N> {
pub fn new(lifetime: u32) -> Self {
Self {
entries: [SeenEntry::default(); N],
next: 0,
tick: 0,
lifetime,
}
}
pub fn tick(&mut self) {
self.tick = self.tick.wrapping_add(1);
}
pub fn is_duplicate(&self, message_id: u32) -> bool {
for entry in &self.entries {
if entry.valid && entry.message_id == message_id {
let age = self.tick.wrapping_sub(entry.seen_at);
if age <= self.lifetime {
return true;
}
}
}
false
}
pub fn check_and_mark(&mut self, message_id: u32) -> bool {
for entry in &mut self.entries {
if entry.valid && entry.message_id == message_id {
let age = self.tick.wrapping_sub(entry.seen_at);
if age <= self.lifetime {
return true; }
}
}
self.mark_seen(message_id);
false
}
pub fn mark_seen(&mut self, message_id: u32) {
for entry in &mut self.entries {
if !entry.valid {
entry.message_id = message_id;
entry.seen_at = self.tick;
entry.valid = true;
return;
}
let age = self.tick.wrapping_sub(entry.seen_at);
if age > self.lifetime {
entry.message_id = message_id;
entry.seen_at = self.tick;
entry.valid = true;
return;
}
}
self.entries[self.next] = SeenEntry {
message_id,
seen_at: self.tick,
valid: true,
};
self.next = (self.next + 1) % N;
}
pub fn clear(&mut self) {
for entry in &mut self.entries {
entry.valid = false;
}
self.next = 0;
}
pub fn len(&self) -> usize {
self.entries
.iter()
.filter(|e| e.valid && self.tick.wrapping_sub(e.seen_at) <= self.lifetime)
.count()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_seen_cache_basic() {
let mut cache: SeenCache<8> = SeenCache::new(100);
assert!(!cache.check_and_mark(0x0001_0001));
assert!(cache.check_and_mark(0x0001_0001));
assert!(!cache.check_and_mark(0x0001_0002));
}
#[test]
fn test_seen_cache_expiry() {
let mut cache: SeenCache<8> = SeenCache::new(10);
assert!(!cache.check_and_mark(0x0001_0001));
assert!(cache.check_and_mark(0x0001_0001));
for _ in 0..15 {
cache.tick();
}
assert!(!cache.check_and_mark(0x0001_0001));
}
#[test]
fn test_seen_cache_lru() {
let mut cache: SeenCache<4> = SeenCache::new(1000);
for i in 0..4 {
assert!(!cache.check_and_mark(i));
}
for i in 0..4 {
assert!(cache.check_and_mark(i));
}
for i in 4..8 {
assert!(!cache.check_and_mark(i));
}
assert!(!cache.check_and_mark(0)); }
#[test]
fn test_seen_cache_clear() {
let mut cache: SeenCache<8> = SeenCache::new(100);
cache.mark_seen(0x0001_0001);
cache.mark_seen(0x0001_0002);
assert_eq!(cache.len(), 2);
cache.clear();
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
}
#[test]
fn test_seen_cache_len() {
let mut cache: SeenCache<8> = SeenCache::new(5);
assert_eq!(cache.len(), 0);
cache.mark_seen(1);
cache.mark_seen(2);
cache.mark_seen(3);
assert_eq!(cache.len(), 3);
for _ in 0..10 {
cache.tick();
}
assert_eq!(cache.len(), 0);
}
}