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			PenaltyReason::AuthFailure => Duration::from_secs(3600), // 1 hour
57			PenaltyReason::TokenVerificationFailure => Duration::from_secs(3600), // 1 hour
58			PenaltyReason::SuspiciousActivity => Duration::from_secs(7200), // 2 hours
59			PenaltyReason::RepeatedViolation => Duration::from_secs(86400), // 24 hours
60		}
61	}
62}
63
64/// Reason for incrementing the PoW counter
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum PowPenaltyReason {
67	/// CONN action failed signature verification
68	ConnSignatureFailure,
69	/// CONN received while another pending from same issuer
70	ConnDuplicatePending,
71	/// CONN action was rejected by user or policy
72	ConnRejected,
73	/// CONN action failed PoW check (insufficient proof of work)
74	ConnPowCheckFailed,
75}
76
77impl PowPenaltyReason {
78	/// Whether this reason should affect network-level counter too
79	pub fn affects_network(&self) -> bool {
80		match self {
81			PowPenaltyReason::ConnSignatureFailure => true,
82			PowPenaltyReason::ConnDuplicatePending => true,
83			PowPenaltyReason::ConnRejected => false, // Individual only
84			PowPenaltyReason::ConnPowCheckFailed => true, // Repeated PoW failures affect network
85		}
86	}
87}
88
89/// Ban entry stored in the ban list
90#[derive(Debug, Clone)]
91pub struct BanEntry {
92	/// Address key that is banned
93	pub key: AddressKey,
94	/// Reason for the ban
95	pub reason: PenaltyReason,
96	/// When the ban was created
97	pub created_at: Instant,
98	/// When the ban expires (None = permanent)
99	pub expires_at: Option<Instant>,
100}
101
102impl BanEntry {
103	/// Check if this ban has expired
104	pub fn is_expired(&self) -> bool {
105		self.expires_at.is_some_and(|exp| Instant::now() >= exp)
106	}
107
108	/// Get remaining duration until ban expires
109	pub fn remaining_duration(&self) -> Option<Duration> {
110		self.expires_at.map(|exp| {
111			let now = Instant::now();
112			if now >= exp {
113				Duration::ZERO
114			} else {
115				exp - now
116			}
117		})
118	}
119}
120
121/// Statistics about the rate limiter
122#[derive(Debug, Clone, Default)]
123pub struct RateLimiterStats {
124	/// Number of tracked addresses
125	pub tracked_addresses: usize,
126	/// Number of active bans
127	pub active_bans: usize,
128	/// Total requests that were rate limited
129	pub total_requests_limited: u64,
130	/// Total bans issued
131	pub total_bans_issued: u64,
132	/// Current PoW counter entries (individual level)
133	pub pow_individual_entries: usize,
134	/// Current PoW counter entries (network level)
135	pub pow_network_entries: usize,
136}
137
138/// Internal API for programmatic rate limit management
139pub trait RateLimitApi: Send + Sync {
140	/// Query current limit status for an address at all hierarchical levels
141	fn get_status(
142		&self,
143		addr: &IpAddr,
144		category: &str,
145	) -> ClResult<Vec<(AddressKey, RateLimitStatus)>>;
146
147	/// Manually consume quota (increase usage) - e.g., after auth failure
148	fn penalize(&self, addr: &IpAddr, reason: PenaltyReason, amount: u32) -> ClResult<()>;
149
150	/// Decrease penalty count (grant extra quota) - e.g., after successful CAPTCHA
151	fn grant(&self, addr: &IpAddr, amount: u32) -> ClResult<()>;
152
153	/// Reset limits for an address at all levels
154	fn reset(&self, addr: &IpAddr) -> ClResult<()>;
155
156	/// Temporarily ban an address (all hierarchical levels)
157	fn ban(&self, addr: &IpAddr, duration: Duration, reason: PenaltyReason) -> ClResult<()>;
158
159	/// Unban an address
160	fn unban(&self, addr: &IpAddr) -> ClResult<()>;
161
162	/// Check if an address is banned
163	fn is_banned(&self, addr: &IpAddr) -> bool;
164
165	/// List all currently banned addresses
166	fn list_bans(&self) -> Vec<BanEntry>;
167
168	/// Get statistics about rate limiter state
169	fn stats(&self) -> RateLimiterStats;
170
171	// === Proof-of-Work Counter API ===
172
173	/// Get current PoW requirement for address (max of individual + network level)
174	fn get_pow_requirement(&self, addr: &IpAddr) -> u32;
175
176	/// Increment PoW counter for address
177	fn increment_pow_counter(&self, addr: &IpAddr, reason: PowPenaltyReason) -> ClResult<()>;
178
179	/// Decrement PoW counter (after successful CONN, with decay over time)
180	fn decrement_pow_counter(&self, addr: &IpAddr, amount: u32) -> ClResult<()>;
181
182	/// Verify proof-of-work on action token
183	fn verify_pow(&self, addr: &IpAddr, token: &str) -> Result<(), PowError>;
184}
185
186// vim: ts=4