//! Anti-abuse mechanisms for preventing slot blocking and spam. use std::collections::HashMap; use std::time::{SystemTime, UNIX_EPOCH}; use sha2::{Digest, Sha256}; /// Rate limiting configuration. #[derive(Debug, Clone)] pub struct RateLimits { /// Max reservations per sender per hour. pub max_reservations_per_hour: u8, /// Max pending (unconfirmed) reservations per sender. pub max_pending_reservations: u8, /// Min time between reservations (seconds). pub reservation_cooldown_secs: u32, /// Max queries per sender per minute. pub max_queries_per_minute: u8, } impl Default for RateLimits { fn default() -> Self { Self { max_reservations_per_hour: 3, max_pending_reservations: 2, reservation_cooldown_secs: 300, max_queries_per_minute: 10, } } } /// Tracks sender activity for rate limiting. #[derive(Debug, Default)] pub struct RateLimiter { limits: RateLimits, /// sender_address -> activity activity: HashMap<[u8; 16], SenderActivity>, } #[derive(Debug, Default)] struct SenderActivity { /// Timestamps of reservations in last hour. reservation_times: Vec, /// Count of pending reservations. pending_count: u8, /// Timestamp of last reservation. last_reservation: u64, /// Query timestamps in last minute. query_times: Vec, } impl RateLimiter { /// Create with default limits. pub fn new() -> Self { Self::default() } /// Create with custom limits. pub fn with_limits(limits: RateLimits) -> Self { Self { limits, activity: HashMap::new(), } } /// Check if a reservation is allowed. pub fn check_reservation(&mut self, sender: &[u8; 16]) -> RateLimitResult { let now = now(); let activity = self.activity.entry(*sender).or_default(); // Clean old entries activity.reservation_times.retain(|&t| now - t < 3600); // Check cooldown if now - activity.last_reservation < u64::from(self.limits.reservation_cooldown_secs) { return RateLimitResult::Cooldown { wait_secs: self.limits.reservation_cooldown_secs - (now - activity.last_reservation) as u32, }; } // Check hourly limit if activity.reservation_times.len() >= self.limits.max_reservations_per_hour as usize { return RateLimitResult::HourlyLimitReached; } // Check pending limit if activity.pending_count >= self.limits.max_pending_reservations { return RateLimitResult::TooManyPending; } RateLimitResult::Allowed } /// Record a reservation attempt. pub fn record_reservation(&mut self, sender: &[u8; 16]) { let now = now(); let activity = self.activity.entry(*sender).or_default(); activity.reservation_times.push(now); activity.last_reservation = now; activity.pending_count = activity.pending_count.saturating_add(1); } /// Record reservation confirmed/completed (reduce pending). pub fn record_reservation_resolved(&mut self, sender: &[u8; 16]) { if let Some(activity) = self.activity.get_mut(sender) { activity.pending_count = activity.pending_count.saturating_sub(1); } } /// Check if a query is allowed. pub fn check_query(&mut self, sender: &[u8; 16]) -> RateLimitResult { let now = now(); let activity = self.activity.entry(*sender).or_default(); // Clean old entries activity.query_times.retain(|&t| now - t < 60); if activity.query_times.len() >= self.limits.max_queries_per_minute as usize { return RateLimitResult::QueryLimitReached; } RateLimitResult::Allowed } /// Record a query. pub fn record_query(&mut self, sender: &[u8; 16]) { let now = now(); let activity = self.activity.entry(*sender).or_default(); activity.query_times.push(now); } /// Prune old activity data. pub fn prune(&mut self) { let now = now(); self.activity.retain(|_, a| { a.reservation_times.retain(|&t| now - t < 3600); a.query_times.retain(|&t| now - t < 60); !a.reservation_times.is_empty() || !a.query_times.is_empty() || a.pending_count > 0 }); } } /// Result of rate limit check. #[derive(Debug, Clone, PartialEq, Eq)] pub enum RateLimitResult { /// Request allowed. Allowed, /// Must wait before next reservation. Cooldown { wait_secs: u32 }, /// Hourly reservation limit reached. HourlyLimitReached, /// Too many pending reservations. TooManyPending, /// Query rate limit reached. QueryLimitReached, } impl RateLimitResult { pub fn is_allowed(&self) -> bool { matches!(self, RateLimitResult::Allowed) } } /// Proof-of-work for reservation requests. #[derive(Debug, Clone)] pub struct ProofOfWork { /// Nonce that produces valid hash. pub nonce: u64, /// Required difficulty (leading zero bits). pub difficulty: u8, } impl ProofOfWork { /// Default difficulty (20 bits ≈ 1-2 seconds on modern CPU). pub const DEFAULT_DIFFICULTY: u8 = 20; /// Generate proof-of-work for a reservation. pub fn generate(reservation_id: &[u8; 16], difficulty: u8) -> Self { let mut nonce = 0u64; loop { if Self::check_hash(reservation_id, nonce, difficulty) { return Self { nonce, difficulty }; } nonce = nonce.wrapping_add(1); } } /// Verify proof-of-work. pub fn verify(&self, reservation_id: &[u8; 16]) -> bool { Self::check_hash(reservation_id, self.nonce, self.difficulty) } fn check_hash(reservation_id: &[u8; 16], nonce: u64, difficulty: u8) -> bool { let mut hasher = Sha256::new(); hasher.update(reservation_id); hasher.update(&nonce.to_le_bytes()); let hash = hasher.finalize(); leading_zero_bits(&hash) >= difficulty } } /// Count leading zero bits in a byte slice. fn leading_zero_bits(data: &[u8]) -> u8 { let mut count = 0u8; for byte in data { if *byte == 0 { count += 8; } else { count += byte.leading_zeros() as u8; break; } } count } /// Sender reputation tracking. #[derive(Debug, Clone, Default)] pub struct SenderReputation { pub address: [u8; 16], pub reservations_made: u32, pub reservations_honored: u32, pub reservations_cancelled: u32, pub no_shows: u32, pub last_no_show: Option, } impl SenderReputation { /// Create for a new sender. pub fn new(address: [u8; 16]) -> Self { Self { address, ..Default::default() } } /// Calculate honor rate (0.0 to 1.0). pub fn honor_rate(&self) -> f32 { if self.reservations_made == 0 { return 0.5; // Neutral for new users } (self.reservations_honored as f32) / (self.reservations_made as f32) } /// Check if sender should be blocked. pub fn is_blocked(&self) -> bool { self.no_shows >= 3 || (self.reservations_made >= 5 && self.honor_rate() < 0.5) } /// Record a completed reservation. pub fn record_honored(&mut self) { self.reservations_made += 1; self.reservations_honored += 1; } /// Record a cancelled reservation (with notice). pub fn record_cancelled(&mut self) { self.reservations_made += 1; self.reservations_cancelled += 1; } /// Record a no-show. pub fn record_no_show(&mut self) { self.reservations_made += 1; self.no_shows += 1; self.last_no_show = Some(now()); } } /// Reputation store. #[derive(Debug, Default)] pub struct ReputationStore { reputations: HashMap<[u8; 16], SenderReputation>, } impl ReputationStore { pub fn new() -> Self { Self::default() } /// Get or create reputation for a sender. pub fn get_or_create(&mut self, address: [u8; 16]) -> &mut SenderReputation { self.reputations .entry(address) .or_insert_with(|| SenderReputation::new(address)) } /// Get reputation (read-only). pub fn get(&self, address: &[u8; 16]) -> Option<&SenderReputation> { self.reputations.get(address) } /// Check if sender is blocked. pub fn is_blocked(&self, address: &[u8; 16]) -> bool { self.reputations .get(address) .map(|r| r.is_blocked()) .unwrap_or(false) } /// Get honor rate (0.5 for unknown). pub fn honor_rate(&self, address: &[u8; 16]) -> f32 { self.reputations .get(address) .map(|r| r.honor_rate()) .unwrap_or(0.5) } } /// Blocklist entry. #[derive(Debug, Clone)] pub struct BlocklistEntry { pub blocked_address: [u8; 16], pub reason: BlockReason, pub reported_by: [u8; 16], pub signature: Vec, pub timestamp: u64, } /// Reason for blocking. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum BlockReason { NoShow = 1, Spam = 2, Harassment = 3, FakeIdentity = 4, } /// Therapist-defined reservation policy. #[derive(Debug, Clone)] pub struct TherapistPolicy { /// Max pending reservations from new senders. pub max_pending_new: u8, /// Max pending from established senders. pub max_pending_established: u8, /// Require this verification level for reservations. pub min_verification_level: u8, /// Auto-reject senders with honor rate below this. pub min_honor_rate: f32, /// Require proof-of-work. pub require_pow: bool, /// PoW difficulty (if required). pub pow_difficulty: u8, } impl Default for TherapistPolicy { fn default() -> Self { Self { max_pending_new: 1, max_pending_established: 3, min_verification_level: 0, min_honor_rate: 0.5, require_pow: true, pow_difficulty: ProofOfWork::DEFAULT_DIFFICULTY, } } } impl TherapistPolicy { /// Check if a reservation request meets policy. pub fn check( &self, sender_reputation: &SenderReputation, sender_verification_level: u8, pow: Option<&ProofOfWork>, reservation_id: &[u8; 16], ) -> PolicyResult { // Check verification level if sender_verification_level < self.min_verification_level { return PolicyResult::InsufficientVerification; } // Check honor rate if sender_reputation.honor_rate() < self.min_honor_rate { return PolicyResult::LowReputation; } // Check blocked if sender_reputation.is_blocked() { return PolicyResult::Blocked; } // Check proof-of-work if self.require_pow { match pow { Some(p) if p.difficulty >= self.pow_difficulty && p.verify(reservation_id) => {} Some(_) => return PolicyResult::InvalidPoW, None => return PolicyResult::MissingPoW, } } PolicyResult::Allowed } } /// Result of policy check. #[derive(Debug, Clone, PartialEq, Eq)] pub enum PolicyResult { Allowed, InsufficientVerification, LowReputation, Blocked, MissingPoW, InvalidPoW, } impl PolicyResult { pub fn is_allowed(&self) -> bool { matches!(self, PolicyResult::Allowed) } } fn now() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_secs() } #[cfg(test)] mod tests { use super::*; #[test] fn rate_limiter_allows_first_reservation() { let mut limiter = RateLimiter::new(); let sender = [1u8; 16]; assert!(limiter.check_reservation(&sender).is_allowed()); } #[test] fn rate_limiter_enforces_cooldown() { let mut limiter = RateLimiter::with_limits(RateLimits { reservation_cooldown_secs: 300, ..Default::default() }); let sender = [2u8; 16]; limiter.record_reservation(&sender); let result = limiter.check_reservation(&sender); assert!(matches!(result, RateLimitResult::Cooldown { .. })); } #[test] fn rate_limiter_enforces_hourly_limit() { let mut limiter = RateLimiter::with_limits(RateLimits { max_reservations_per_hour: 2, reservation_cooldown_secs: 0, ..Default::default() }); let sender = [3u8; 16]; limiter.record_reservation(&sender); limiter.record_reservation(&sender); assert_eq!(limiter.check_reservation(&sender), RateLimitResult::HourlyLimitReached); } #[test] fn pow_generation_and_verification() { let reservation_id = [42u8; 16]; let pow = ProofOfWork::generate(&reservation_id, 8); // Low difficulty for test assert!(pow.verify(&reservation_id)); assert!(!pow.verify(&[0u8; 16])); // Wrong ID } #[test] fn reputation_tracking() { let mut rep = SenderReputation::new([5u8; 16]); rep.record_honored(); rep.record_honored(); rep.record_no_show(); assert_eq!(rep.reservations_made, 3); assert_eq!(rep.honor_rate(), 2.0 / 3.0); assert!(!rep.is_blocked()); rep.record_no_show(); rep.record_no_show(); assert!(rep.is_blocked()); // 3 no-shows } #[test] fn policy_check_pow() { let policy = TherapistPolicy { require_pow: true, pow_difficulty: 8, ..Default::default() }; let rep = SenderReputation::new([6u8; 16]); let reservation_id = [7u8; 16]; // No PoW assert_eq!( policy.check(&rep, 0, None, &reservation_id), PolicyResult::MissingPoW ); // Valid PoW let pow = ProofOfWork::generate(&reservation_id, 8); assert_eq!( policy.check(&rep, 0, Some(&pow), &reservation_id), PolicyResult::Allowed ); } #[test] fn policy_check_verification_level() { let policy = TherapistPolicy { min_verification_level: 2, require_pow: false, ..Default::default() }; let rep = SenderReputation::new([8u8; 16]); let reservation_id = [9u8; 16]; assert_eq!( policy.check(&rep, 1, None, &reservation_id), PolicyResult::InsufficientVerification ); assert_eq!( policy.check(&rep, 2, None, &reservation_id), PolicyResult::Allowed ); } }