use std::{
collections::HashSet,
net::SocketAddr,
time::{Duration, Instant},
};
use dht_rpc::IdBytes;
const MIN_CONNECTION_TIME: Duration = Duration::from_secs(15);
#[derive(Debug, Clone, Default)]
pub enum ConnectionState {
#[default]
Idle,
Queued,
Waiting,
Connecting,
Connected { since: Instant },
}
impl ConnectionState {
pub fn is_idle(&self) -> bool {
matches!(self, Self::Idle)
}
pub fn is_queued(&self) -> bool {
matches!(self, Self::Queued)
}
pub fn is_waiting(&self) -> bool {
matches!(self, Self::Waiting)
}
pub fn is_connecting(&self) -> bool {
matches!(self, Self::Connecting)
}
pub fn is_connected(&self) -> bool {
matches!(self, Self::Connected { .. })
}
pub fn is_busy(&self) -> bool {
matches!(self, Self::Queued | Self::Waiting | Self::Connecting)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum PeerOrigin {
#[default]
Discovered,
Explicit,
Incoming,
}
impl PeerOrigin {
pub fn is_client(&self) -> bool {
matches!(self, Self::Discovered | Self::Explicit)
}
pub fn is_explicit(&self) -> bool {
matches!(self, Self::Explicit)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum TrustLevel {
#[default]
Unknown,
Proven,
Banned,
}
impl TrustLevel {
pub fn is_proven(&self) -> bool {
matches!(self, Self::Proven)
}
pub fn is_banned(&self) -> bool {
matches!(self, Self::Banned)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(u8)]
pub enum Priority {
VeryLow = 0,
Low = 1,
#[default]
Normal = 2,
High = 3,
VeryHigh = 4,
}
#[derive(Debug)]
pub struct PeerInfo {
pub public_key: IdBytes,
pub relay_addresses: Vec<SocketAddr>,
pub reconnecting: bool,
pub trust: TrustLevel,
pub attempts: u32,
pub priority: Priority,
pub state: ConnectionState,
pub origin: PeerOrigin,
pub topics: HashSet<IdBytes>,
}
impl PeerInfo {
pub fn new(public_key: IdBytes) -> Self {
Self {
public_key,
relay_addresses: Vec::new(),
reconnecting: true,
trust: TrustLevel::Unknown,
attempts: 0,
priority: Priority::Normal,
state: ConnectionState::Idle,
origin: PeerOrigin::Discovered,
topics: HashSet::new(),
}
}
pub fn connected(&mut self) {
self.state = ConnectionState::Connected {
since: Instant::now(),
};
self.trust = TrustLevel::Proven;
self.update_priority();
}
pub fn disconnected(&mut self) {
if let ConnectionState::Connected { since } = self.state {
if since.elapsed() < MIN_CONNECTION_TIME {
self.attempts = self.attempts.saturating_add(1);
} else {
self.attempts = 0;
}
} else {
self.attempts = self.attempts.saturating_add(1);
}
self.state = ConnectionState::Idle;
self.update_priority();
}
pub fn add_relay_addresses(&mut self, relay_addresses: Vec<SocketAddr>) -> &mut Self {
for addr in relay_addresses {
if !self.relay_addresses.contains(&addr) {
self.relay_addresses.push(addr);
}
}
self
}
pub fn add_topic(&mut self, topic: IdBytes) -> &mut Self {
self.topics.insert(topic);
self
}
pub fn remove_topic(&mut self, topic: &IdBytes) {
self.topics.remove(topic);
}
pub fn ban(&mut self) {
self.trust = TrustLevel::Banned;
self.priority = Priority::VeryLow;
}
pub fn update_priority(&mut self) -> bool {
if self.trust.is_banned() {
self.priority = Priority::VeryLow;
return false;
}
let old_priority = self.priority;
self.priority = self.calculate_priority();
self.priority != Priority::VeryLow
&& (self.priority != old_priority || !self.state.is_queued())
}
fn calculate_priority(&self) -> Priority {
match (self.trust.is_proven(), self.attempts) {
(true, 0) => Priority::VeryHigh,
(true, 1) => Priority::VeryHigh,
(true, 2) => Priority::High,
(true, 3) => Priority::Normal,
(true, _) => Priority::Low,
(false, 0) => Priority::Normal,
(false, 1) => Priority::High, (false, 2) => Priority::Normal,
(false, 3) => Priority::Low,
(false, _) => Priority::VeryLow,
}
}
pub fn reset(&mut self) {
if !self.trust.is_proven() {
self.attempts = 0;
}
self.update_priority();
}
pub fn should_gc(&self) -> bool {
if self.state.is_busy() || self.origin.is_explicit() || !self.topics.is_empty() {
return false;
}
self.trust.is_banned() || self.attempts > 10
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_priority_ordering() {
assert!(Priority::VeryLow < Priority::Low);
assert!(Priority::Low < Priority::Normal);
assert!(Priority::Normal < Priority::High);
assert!(Priority::High < Priority::VeryHigh);
}
#[test]
fn test_new_peer_priority() {
let info = PeerInfo::new(IdBytes::random());
assert_eq!(info.priority, Priority::Normal);
assert_eq!(info.trust, TrustLevel::Unknown);
assert_eq!(info.attempts, 0);
}
#[test]
fn test_connected_sets_proven() {
let mut info = PeerInfo::new(IdBytes::random());
assert!(!info.trust.is_proven());
info.connected();
assert!(info.trust.is_proven());
assert_eq!(info.priority, Priority::VeryHigh);
}
#[test]
fn test_ban_sets_very_low() {
let mut info = PeerInfo::new(IdBytes::random());
info.ban();
assert!(info.trust.is_banned());
assert_eq!(info.priority, Priority::VeryLow);
}
#[test]
fn test_attempts_decrease_priority() {
let mut info = PeerInfo::new(IdBytes::random());
assert_eq!(info.calculate_priority(), Priority::Normal);
info.attempts = 1;
assert_eq!(info.calculate_priority(), Priority::High);
info.attempts = 2;
assert_eq!(info.calculate_priority(), Priority::Normal);
info.attempts = 3;
assert_eq!(info.calculate_priority(), Priority::Low);
info.attempts = 5;
assert_eq!(info.calculate_priority(), Priority::VeryLow);
}
#[test]
fn test_proven_peer_priority() {
let mut info = PeerInfo::new(IdBytes::random());
info.trust = TrustLevel::Proven;
assert_eq!(info.calculate_priority(), Priority::VeryHigh);
info.attempts = 3;
assert_eq!(info.calculate_priority(), Priority::Normal);
info.attempts = 5;
assert_eq!(info.calculate_priority(), Priority::Low);
}
#[test]
fn test_should_gc() {
let mut info = PeerInfo::new(IdBytes::random());
assert!(!info.should_gc());
info.ban();
assert!(info.should_gc());
info.trust = TrustLevel::Unknown;
info.topics.insert(IdBytes::random());
assert!(!info.should_gc());
info.topics.clear();
info.origin = PeerOrigin::Explicit;
assert!(!info.should_gc());
info.origin = PeerOrigin::Discovered;
info.state = ConnectionState::Queued;
assert!(!info.should_gc());
}
}