pub mod cards;
pub mod fields;
pub mod model;
pub mod project;
pub mod read;
pub mod sweep;
pub mod values;
pub mod write_policy;
use std::collections::{HashMap, VecDeque};
use std::sync::Mutex;
use std::time::{Duration, Instant};
use vti_common::store::KeyspaceHandle;
use model::Settings;
pub(crate) static WRITE_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
pub struct Directory {
ks: KeyspaceHandle,
settings: Settings,
recent: Mutex<HashMap<String, VecDeque<Instant>>>,
}
impl Directory {
pub fn new(ks: KeyspaceHandle, settings: Settings) -> Self {
Self {
ks,
settings,
recent: Mutex::new(HashMap::new()),
}
}
pub fn ks(&self) -> &KeyspaceHandle {
&self.ks
}
pub fn settings(&self) -> Settings {
self.settings
}
pub fn admit_list_request(&self, did: &str) -> bool {
let now = Instant::now();
let window = Duration::from_secs(60);
let mut recent = self.recent.lock().unwrap_or_else(|p| p.into_inner());
if recent.len() > 10_000 {
recent.retain(|_, q| q.back().is_some_and(|t| now.duration_since(*t) < window));
}
let q = recent.entry(did.to_string()).or_default();
while q.front().is_some_and(|t| now.duration_since(*t) >= window) {
q.pop_front();
}
if q.len() >= self.settings.list_rate_per_minute as usize {
return false;
}
q.push_back(now);
true
}
}
#[cfg(test)]
pub(crate) mod tests {
use vti_common::store::KeyspaceHandle;
pub(crate) async fn scratch() -> KeyspaceHandle {
let dir = tempfile::tempdir().unwrap();
let store = vti_common::store::test_fixture::open_with_keyspaces(
&dir.path().join("db"),
&["directory"],
)
.unwrap();
let ks = store.keyspace("directory").unwrap();
std::mem::forget(dir);
std::mem::forget(store);
ks
}
#[test]
fn vti_dir_114_reads_are_rate_limited_per_caller() {
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
let ks = rt.block_on(scratch());
let d = super::Directory::new(
ks,
super::Settings {
list_rate_per_minute: 2,
..super::Settings::default()
},
);
assert!(d.admit_list_request("did:example:a"));
assert!(d.admit_list_request("did:example:a"));
assert!(!d.admit_list_request("did:example:a"));
assert!(d.admit_list_request("did:example:b"), "counted per caller");
}
}