Skip to main content

cloudillo_core/rate_limit/
api.rs

1//! Rate Limiting Internal API
2//!
3//! Traits and types for programmatic rate limit management.
4
5use std::net::IpAddr;
6use std::time::{Duration, Instant};
7
8use super::error::PowError;
9use super::extractors::AddressKey;
10use crate::prelude::*;
11
12/// Current status of rate limiting for an address at a specific level
13#[derive(Debug, Clone)]
14pub struct RateLimitStatus {
15	/// Whether this address is currently rate limited
16	pub is_limited: bool,
17	/// Remaining requests before limit kicks in (if not limited)
18	pub remaining: Option<u32>,
19	/// When the limit will reset (if limited)
20	pub reset_at: Option<Instant>,
21	/// Total quota for this period
22	pub quota: u32,
23	/// Whether address is currently banned
24	pub is_banned: bool,
25	/// When ban expires (if banned)
26	pub ban_expires_at: Option<Instant>,
27}
28
29/// Reason for a penalty
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum PenaltyReason {
32	/// Failed authentication attempt
33	AuthFailure,
34	/// Invalid action token
35	TokenVerificationFailure,
36	/// Suspicious request pattern
37	SuspiciousActivity,
38	/// Rate limit exceeded multiple times
39	RepeatedViolation,
40}
41
42impl PenaltyReason {
43	/// Get the number of failures before auto-ban for this reason
44	pub fn failures_to_ban(&self) -> u32 {
45		match self {
46			PenaltyReason::AuthFailure => 5,
47			PenaltyReason::TokenVerificationFailure => 3,
48			PenaltyReason::SuspiciousActivity => 2,
49			PenaltyReason::RepeatedViolation => 1,
50		}
51	}
52
53	/// Get the default ban duration for this reason
54	pub fn ban_duration(&self) -> Duration {
55		match self {
56			// 1 hour
57			PenaltyReason::AuthFailure | PenaltyReason::TokenVerificationFailure => {
58				Duration::from_secs(3600)
59			}
60			PenaltyReason::SuspiciousActivity => Duration::from_secs(7200), // 2 hours
61			PenaltyReason::RepeatedViolation => Duration::from_secs(86400), // 24 hours
62		}
63	}
64}
65
66/// Reason for incrementing the PoW counter
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum PowPenaltyReason {
69	/// CONN action failed signature verification
70	ConnSignatureFailure,
71	/// CONN received while another pending from same issuer
72	ConnDuplicatePending,
73	/// CONN action was rejected by user or policy
74	ConnRejected,
75	/// CONN action failed PoW check (insufficient proof of work)
76	ConnPowCheckFailed,
77}
78
79impl PowPenaltyReason {
80	/// Whether this reason should affect network-level counter too
81	pub fn affects_network(&self) -> bool {
82		match self {
83			PowPenaltyReason::ConnRejected => false, // Individual only
84			// Network-affecting reasons
85			PowPenaltyReason::ConnSignatureFailure
86			| PowPenaltyReason::ConnDuplicatePending
87			| PowPenaltyReason::ConnPowCheckFailed => true,
88		}
89	}
90}
91
92/// Ban entry stored in the ban list
93#[derive(Debug, Clone)]
94pub struct BanEntry {
95	/// Address key that is banned
96	pub key: AddressKey,
97	/// Reason for the ban
98	pub reason: PenaltyReason,
99	/// When the ban was created
100	pub created_at: Instant,
101	/// When the ban expires (None = permanent)
102	pub expires_at: Option<Instant>,
103}
104
105impl BanEntry {
106	/// Check if this ban has expired
107	pub fn is_expired(&self) -> bool {
108		self.expires_at.is_some_and(|exp| Instant::now() >= exp)
109	}
110
111	/// Get remaining duration until ban expires
112	pub fn remaining_duration(&self) -> Option<Duration> {
113		self.expires_at.map(|exp| {
114			let now = Instant::now();
115			if now >= exp {
116				Duration::ZERO
117			} else {
118				exp - now
119			}
120		})
121	}
122}
123
124/// Statistics about the rate limiter
125#[derive(Debug, Clone, Default)]
126pub struct RateLimiterStats {
127	/// Number of tracked addresses
128	pub tracked_addresses: usize,
129	/// Number of active bans
130	pub active_bans: usize,
131	/// Total requests that were rate limited
132	pub total_requests_limited: u64,
133	/// Total bans issued
134	pub total_bans_issued: u64,
135	/// Current PoW counter entries (individual level)
136	pub pow_individual_entries: usize,
137	/// Current PoW counter entries (network level)
138	pub pow_network_entries: usize,
139}
140
141/// Internal API for programmatic rate limit management
142pub trait RateLimitApi: Send + Sync {
143	/// Query current limit status for an address at all hierarchical levels
144	fn get_status(
145		&self,
146		addr: &IpAddr,
147		category: &str,
148	) -> ClResult<Vec<(AddressKey, RateLimitStatus)>>;
149
150	/// Manually consume quota (increase usage) - e.g., after auth failure
151	fn penalize(&self, addr: &IpAddr, reason: PenaltyReason, amount: u32) -> ClResult<()>;
152
153	/// Decrease penalty count (grant extra quota) - e.g., after successful CAPTCHA
154	fn grant(&self, addr: &IpAddr, amount: u32) -> ClResult<()>;
155
156	/// Reset limits for an address at all levels
157	fn reset(&self, addr: &IpAddr) -> ClResult<()>;
158
159	/// Temporarily ban an address (all hierarchical levels)
160	fn ban(&self, addr: &IpAddr, duration: Duration, reason: PenaltyReason) -> ClResult<()>;
161
162	/// Unban an address
163	fn unban(&self, addr: &IpAddr) -> ClResult<()>;
164
165	/// Check if an address is banned
166	fn is_banned(&self, addr: &IpAddr) -> bool;
167
168	/// List all currently banned addresses
169	fn list_bans(&self) -> Vec<BanEntry>;
170
171	/// Get statistics about rate limiter state
172	fn stats(&self) -> RateLimiterStats;
173
174	// === Proof-of-Work Counter API ===
175
176	/// Get current PoW requirement for address (max of individual + network level)
177	fn get_pow_requirement(&self, addr: &IpAddr) -> u32;
178
179	/// Increment PoW counter for address
180	fn increment_pow_counter(&self, addr: &IpAddr, reason: PowPenaltyReason) -> ClResult<()>;
181
182	/// Decrement PoW counter (after successful CONN, with decay over time)
183	fn decrement_pow_counter(&self, addr: &IpAddr, amount: u32) -> ClResult<()>;
184
185	/// Verify proof-of-work on action token
186	fn verify_pow(&self, addr: &IpAddr, token: &str) -> Result<(), PowError>;
187}
188
189// vim: ts=4