use std::collections::HashMap;
use rings_core::dht::Did;
use crate::error::OnionQueueAdmissionReason;
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct PeerQuota {
total: usize,
per_peer: HashMap<Did, usize>,
max_total: usize,
max_per_peer: usize,
}
impl PeerQuota {
pub(crate) fn new(max_total: usize, max_per_peer: usize) -> Self {
Self {
total: 0,
per_peer: HashMap::new(),
max_total,
max_per_peer,
}
}
pub(crate) const fn total(&self) -> usize {
self.total
}
pub(crate) fn peer_total(&self, peer: Did) -> usize {
self.per_peer.get(&peer).copied().unwrap_or_default()
}
pub(crate) fn can_reserve(&self, peer: Did) -> Result<(), OnionQueueAdmissionReason> {
self.successor(peer).map(|_| ())
}
fn successor(&self, peer: Did) -> Result<(usize, usize), OnionQueueAdmissionReason> {
let next_total = self
.total
.checked_add(1)
.ok_or(OnionQueueAdmissionReason::CounterOverflow)?;
let next_peer = self
.peer_total(peer)
.checked_add(1)
.ok_or(OnionQueueAdmissionReason::CounterOverflow)?;
if next_total > self.max_total {
return Err(OnionQueueAdmissionReason::GlobalFull);
}
if next_peer > self.max_per_peer {
return Err(OnionQueueAdmissionReason::PeerFull);
}
Ok((next_total, next_peer))
}
pub(crate) fn reserve(&mut self, peer: Did) -> Result<(), OnionQueueAdmissionReason> {
let (next_total, next_peer) = self.successor(peer)?;
self.total = next_total;
self.per_peer.insert(peer, next_peer);
Ok(())
}
pub(crate) fn release(&mut self, peer: Did) -> bool {
let Some(peer_total) = self.per_peer.get(&peer).copied() else {
return false;
};
let Some(next_total) = self.total.checked_sub(1) else {
return false;
};
let Some(next_peer) = peer_total.checked_sub(1) else {
return false;
};
self.total = next_total;
if next_peer == 0 {
self.per_peer.remove(&peer);
} else {
self.per_peer.insert(peer, next_peer);
}
true
}
pub(crate) fn release_peer(&mut self, peer: Did) -> Option<usize> {
let peer_total = self.per_peer.get(&peer).copied()?;
let next_total = self.total.checked_sub(peer_total)?;
self.total = next_total;
self.per_peer.remove(&peer);
Some(peer_total)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reserve_and_release_preserve_global_and_peer_projection() {
let mut quota = PeerQuota::new(2, 1);
let first = Did::from(1_u32);
let second = Did::from(2_u32);
assert_eq!(quota.reserve(first), Ok(()));
assert_eq!(
quota.reserve(first),
Err(OnionQueueAdmissionReason::PeerFull)
);
assert_eq!(quota.total(), 1);
assert_eq!(quota.peer_total(first), 1);
assert_eq!(quota.reserve(second), Ok(()));
assert_eq!(
quota.reserve(Did::from(3_u32)),
Err(OnionQueueAdmissionReason::GlobalFull)
);
assert!(quota.release(first));
assert!(!quota.release(first));
assert_eq!(quota.total(), 1);
assert_eq!(quota.peer_total(second), 1);
assert_eq!(quota.release_peer(second), Some(1));
assert_eq!(quota.release_peer(second), None);
assert_eq!(quota.total(), 0);
}
}