use rand::random;
use std::{
cmp::Ordering,
collections::BTreeSet,
time::{Duration, SystemTime, UNIX_EPOCH},
};
fn unixtime() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("not to be approaching a spacetime singularity")
.as_secs()
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Id {
unix: u64,
uniq: u64,
}
impl PartialOrd for Id {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Id {
fn cmp(&self, other: &Self) -> Ordering {
(self.unix, self.uniq)
.cmp(&(other.unix, other.uniq))
.reverse()
}
}
impl Id {
pub const fn new(unix: u64, uniq: u64) -> Self {
Self { unix, uniq }
}
pub fn generate() -> Self {
Self::new(unixtime(), random())
}
#[cfg(test)]
const fn max_age(uniq: u64) -> Self {
Self::new(u64::min_value(), uniq)
}
#[cfg(test)]
const fn min_age(uniq: u64) -> Self {
Self::new(u64::max_value(), uniq)
}
const fn least_at(unix: u64) -> Self {
Self::new(unix, u64::max_value())
}
pub const fn timestamp(&self) -> u64 {
self.unix
}
pub const fn unique(&self) -> u64 {
self.uniq
}
}
pub struct Filter {
events: BTreeSet<Id>,
oldest: u64,
}
impl Filter {
pub fn new(dur: Duration) -> Self {
Self {
events: BTreeSet::new(),
oldest: dur.as_secs(),
}
}
pub fn insert(&mut self, event: Id) -> bool {
let expires_at = unixtime().saturating_sub(self.oldest);
self.events.split_off(&Id::least_at(expires_at));
if event.unix <= expires_at {
return false;
}
self.events.insert(event)
}
}
#[cfg(test)]
mod tests {
use super::*;
use quickcheck_macros::quickcheck;
use std::{collections::HashSet, time::Duration};
#[quickcheck]
fn unique_events_can_be_inserted_once(max: Duration, input: HashSet<u64>) -> bool {
let mut f = Filter::new(max);
(input.into_iter())
.map(|uq| Id::min_age(uq))
.all(|id| f.insert(id) && !f.insert(id))
}
#[quickcheck]
fn expired_events_are_always_false(max: Duration, input: HashSet<u64>) -> bool {
let mut f = Filter::new(max);
(input.into_iter())
.map(|uq| Id::max_age(uq))
.all(|id| !f.insert(id))
}
}