use std::collections::VecDeque;
use crate::sync::SyncId;
use crate::{Error, Result};
pub const MAX_SEEN_SYNC_IDS: usize = 4096;
#[derive(Debug, Clone, Default)]
pub struct SeenSyncIds {
seen: VecDeque<SyncId>,
}
impl SeenSyncIds {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn len(&self) -> usize {
self.seen.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.seen.is_empty()
}
#[must_use]
pub fn contains(&self, sync_id: &SyncId) -> bool {
self.seen.contains(sync_id)
}
pub fn record(&mut self, sync_id: SyncId) -> Result<()> {
if self.seen.len() >= MAX_SEEN_SYNC_IDS {
return Err(Error::SyncIdLogFull {
limit: MAX_SEEN_SYNC_IDS,
});
}
self.seen.push_back(sync_id);
Ok(())
}
pub fn check_and_record(&mut self, sync_id: SyncId) -> Result<()> {
if self.contains(&sync_id) {
return Err(Error::SyncAlreadyApplied);
}
self.record(sync_id)
}
pub fn clear(&mut self) {
self.seen.clear();
}
pub fn iter(&self) -> impl Iterator<Item = &SyncId> {
self.seen.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn id(seed: u8) -> SyncId {
[seed; crate::sync::SYNC_ID_LEN]
}
#[test]
fn a_new_id_is_accepted_and_then_recognized() {
let mut log = SeenSyncIds::new();
assert!(log.is_empty());
assert!(!log.contains(&id(1)));
log.check_and_record(id(1))
.expect("the first call must be accepted");
assert_eq!(log.len(), 1);
assert!(log.contains(&id(1)));
}
#[test]
fn a_repeated_id_is_rejected() {
let mut log = SeenSyncIds::new();
log.check_and_record(id(1))
.expect("the first call must be accepted");
let err = log.check_and_record(id(1));
assert!(
matches!(err, Err(Error::SyncAlreadyApplied)),
"actual {err:?}"
);
assert_eq!(
log.len(),
1,
"a repeated identifier must not be recorded twice"
);
}
#[test]
fn many_distinct_ids_are_all_remembered() {
let mut log = SeenSyncIds::new();
for seed in 0..200_u8 {
log.check_and_record(id(seed))
.expect("a new identifier must be accepted");
}
assert_eq!(log.len(), 200);
for seed in 0..200_u8 {
assert!(
log.contains(&id(seed)),
"identifier {seed} was not remembered"
);
}
}
#[test]
fn a_full_log_fails_instead_of_evicting() {
let mut log = SeenSyncIds::new();
for i in 0..MAX_SEEN_SYNC_IDS {
let mut sync_id = [0_u8; crate::sync::SYNC_ID_LEN];
sync_id[..8].copy_from_slice(&(i as u64).to_be_bytes());
log.record(sync_id)
.expect("must be accepted while not full");
}
assert_eq!(log.len(), MAX_SEEN_SYNC_IDS);
let mut fresh = [9_u8; crate::sync::SYNC_ID_LEN];
fresh[..8].copy_from_slice(&u64::MAX.to_be_bytes());
let err = log.record(fresh);
assert!(
matches!(err, Err(Error::SyncIdLogFull { .. })),
"actual {err:?}"
);
let mut oldest = [0_u8; crate::sync::SYNC_ID_LEN];
oldest[..8].copy_from_slice(&0_u64.to_be_bytes());
assert!(log.contains(&oldest), "the oldest identifier was evicted");
}
#[test]
fn clear_gives_up_protection_explicitly() {
let mut log = SeenSyncIds::new();
log.check_and_record(id(1))
.expect("the first call must be accepted");
log.clear();
assert!(log.is_empty());
log.check_and_record(id(1))
.expect("must be accepted again after a reset");
}
#[test]
fn iteration_is_oldest_first() {
let mut log = SeenSyncIds::new();
for seed in 1..=5_u8 {
log.record(id(seed)).expect("must be accepted");
}
let order: Vec<u8> = log.iter().map(|s| s[0]).collect();
assert_eq!(order, vec![1, 2, 3, 4, 5]);
}
}