use std::sync::{Arc, LazyLock, RwLock};
#[derive(Debug, Clone)]
pub struct ResourceLimits {
pub max_key_derivations_per_call: usize,
pub max_encryption_size_bytes: usize,
pub max_signature_size_bytes: usize,
pub max_decryption_size_bytes: usize,
}
impl Default for ResourceLimits {
fn default() -> Self {
Self {
max_key_derivations_per_call: 1000,
max_encryption_size_bytes: 100 * 1024 * 1024,
max_signature_size_bytes: 64 * 1024,
max_decryption_size_bytes: 100 * 1024 * 1024,
}
}
}
impl ResourceLimits {
#[must_use]
pub fn new(
max_key_derivations: usize,
max_encryption_size: usize,
max_signature_size: usize,
max_decryption_size: usize,
) -> Self {
Self {
max_key_derivations_per_call: max_key_derivations,
max_encryption_size_bytes: max_encryption_size,
max_signature_size_bytes: max_signature_size,
max_decryption_size_bytes: max_decryption_size,
}
}
}
pub struct ResourceLimitsManager {
limits: Arc<RwLock<ResourceLimits>>,
}
impl ResourceLimitsManager {
#[must_use]
pub fn new() -> Self {
Self { limits: Arc::new(RwLock::new(ResourceLimits::default())) }
}
#[must_use]
pub fn with_limits(limits: ResourceLimits) -> Self {
Self { limits: Arc::new(RwLock::new(limits)) }
}
pub fn get_limits(&self) -> Result<ResourceLimits> {
self.limits.read().map(|guard| guard.clone()).map_err(|_poison| ResourceError::LockPoisoned)
}
pub fn update_limits(&self, limits: ResourceLimits) -> Result<()> {
let mut guard = self.limits.write().map_err(|_poison| ResourceError::LockPoisoned)?;
*guard = limits;
Ok(())
}
pub fn validate_key_derivation_count(&self, count: usize) -> Result<()> {
let limits = self.get_limits()?;
if count > limits.max_key_derivations_per_call {
return Err(ResourceError::KeyDerivationLimitExceeded {
requested: count,
limit: limits.max_key_derivations_per_call,
});
}
Ok(())
}
pub fn validate_encryption_size(&self, size: usize) -> Result<()> {
let limits = self.get_limits()?;
if size > limits.max_encryption_size_bytes {
return Err(ResourceError::EncryptionSizeLimitExceeded {
requested: size,
limit: limits.max_encryption_size_bytes,
});
}
Ok(())
}
pub fn validate_signature_size(&self, size: usize) -> Result<()> {
let limits = self.get_limits()?;
if size > limits.max_signature_size_bytes {
return Err(ResourceError::SignatureSizeLimitExceeded {
requested: size,
limit: limits.max_signature_size_bytes,
});
}
Ok(())
}
pub fn validate_decryption_size(&self, size: usize) -> Result<()> {
let limits = self.get_limits()?;
if size > limits.max_decryption_size_bytes {
return Err(ResourceError::DecryptionSizeLimitExceeded {
requested: size,
limit: limits.max_decryption_size_bytes,
});
}
Ok(())
}
}
impl Default for ResourceLimitsManager {
fn default() -> Self {
Self::new()
}
}
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum ResourceError {
#[error("Key derivation limit exceeded: requested {requested}, limit {limit}")]
KeyDerivationLimitExceeded {
requested: usize,
limit: usize,
},
#[error("Encryption size limit exceeded: requested {requested}, limit {limit}")]
EncryptionSizeLimitExceeded {
requested: usize,
limit: usize,
},
#[error("Signature size limit exceeded: requested {requested}, limit {limit}")]
SignatureSizeLimitExceeded {
requested: usize,
limit: usize,
},
#[error("Decryption size limit exceeded: requested {requested}, limit {limit}")]
DecryptionSizeLimitExceeded {
requested: usize,
limit: usize,
},
#[error("Resource limits lock poisoned — a thread panicked while holding the lock")]
LockPoisoned,
}
pub type Result<T> = std::result::Result<T, ResourceError>;
static GLOBAL_RESOURCE_LIMITS: LazyLock<ResourceLimitsManager> =
LazyLock::new(ResourceLimitsManager::new);
#[must_use]
pub fn get_global_resource_limits() -> &'static ResourceLimitsManager {
&GLOBAL_RESOURCE_LIMITS
}
pub fn validate_key_derivation_count(count: usize) -> Result<()> {
get_global_resource_limits().validate_key_derivation_count(count)
}
pub fn validate_encryption_size(size: usize) -> Result<()> {
get_global_resource_limits().validate_encryption_size(size)
}
pub fn validate_signature_size(size: usize) -> Result<()> {
get_global_resource_limits().validate_signature_size(size)
}
pub fn validate_decryption_size(size: usize) -> Result<()> {
get_global_resource_limits().validate_decryption_size(size)
}
#[cfg(kani)]
mod kani_proofs {
use super::*;
#[kani::proof]
#[kani::unwind(3)]
fn validate_encryption_size_biconditional() {
let size: usize = kani::any();
let limit: usize = kani::any();
kani::assume(limit > 0);
let limits =
ResourceLimits { max_encryption_size_bytes: limit, ..ResourceLimits::default() };
let manager = ResourceLimitsManager::with_limits(limits);
let result = manager.validate_encryption_size(size);
if size > limit {
kani::assert(result.is_err(), "validate_encryption_size must err when size > limit");
} else {
kani::assert(result.is_ok(), "validate_encryption_size must Ok when size ≤ limit");
}
}
#[kani::proof]
#[kani::unwind(3)]
fn validate_decryption_size_biconditional() {
let size: usize = kani::any();
let limit: usize = kani::any();
kani::assume(limit > 0);
let limits =
ResourceLimits { max_decryption_size_bytes: limit, ..ResourceLimits::default() };
let manager = ResourceLimitsManager::with_limits(limits);
let result = manager.validate_decryption_size(size);
if size > limit {
kani::assert(result.is_err(), "decryption size > limit must Err");
} else {
kani::assert(result.is_ok(), "decryption size ≤ limit must Ok");
}
}
#[kani::proof]
fn validate_key_derivation_count_accepts_zero() {
let limit: usize = kani::any();
kani::assume(limit > 0);
let limits =
ResourceLimits { max_key_derivations_per_call: limit, ..ResourceLimits::default() };
let manager = ResourceLimitsManager::with_limits(limits);
let result = manager.validate_key_derivation_count(0);
kani::assert(result.is_ok(), "count=0 must not trip the KDF limit (any limit > 0)");
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn test_resource_limits_default_succeeds() {
let limits = ResourceLimits::default();
assert_eq!(limits.max_key_derivations_per_call, 1000);
assert_eq!(limits.max_encryption_size_bytes, 100 * 1024 * 1024);
assert_eq!(limits.max_signature_size_bytes, 64 * 1024);
assert_eq!(limits.max_decryption_size_bytes, 100 * 1024 * 1024);
}
#[test]
fn test_resource_limits_new_succeeds() {
let limits = ResourceLimits::new(500, 50 * 1024 * 1024, 32 * 1024, 50 * 1024 * 1024);
assert_eq!(limits.max_key_derivations_per_call, 500);
assert_eq!(limits.max_encryption_size_bytes, 50 * 1024 * 1024);
assert_eq!(limits.max_signature_size_bytes, 32 * 1024);
assert_eq!(limits.max_decryption_size_bytes, 50 * 1024 * 1024);
}
#[test]
fn test_manager_with_custom_limits_succeeds() {
let custom = ResourceLimits::new(200, 1024, 512, 2048);
let manager = ResourceLimitsManager::with_limits(custom);
let limits = manager.get_limits().unwrap();
assert_eq!(limits.max_key_derivations_per_call, 200);
assert_eq!(limits.max_encryption_size_bytes, 1024);
}
#[test]
fn test_manager_update_limits_succeeds() {
let manager = ResourceLimitsManager::new();
assert_eq!(manager.get_limits().unwrap().max_key_derivations_per_call, 1000);
let new_limits = ResourceLimits::new(50, 1024, 512, 2048);
manager.update_limits(new_limits).unwrap();
assert_eq!(manager.get_limits().unwrap().max_key_derivations_per_call, 50);
}
#[test]
fn test_manager_validate_methods_succeeds() {
let custom = ResourceLimits::new(10, 1024, 512, 2048);
let manager = ResourceLimitsManager::with_limits(custom);
assert!(manager.validate_key_derivation_count(10).is_ok());
assert!(manager.validate_key_derivation_count(11).is_err());
assert!(manager.validate_encryption_size(1024).is_ok());
assert!(manager.validate_encryption_size(1025).is_err());
assert!(manager.validate_signature_size(512).is_ok());
assert!(manager.validate_signature_size(513).is_err());
assert!(manager.validate_decryption_size(2048).is_ok());
assert!(manager.validate_decryption_size(2049).is_err());
}
#[test]
fn test_global_validate_functions_succeeds() {
assert!(validate_key_derivation_count(500).is_ok());
assert!(validate_key_derivation_count(1001).is_err());
assert!(validate_encryption_size(1024).is_ok());
assert!(validate_signature_size(1024).is_ok());
assert!(validate_decryption_size(1024).is_ok());
}
#[test]
fn test_resource_error_display_fails() {
let err = ResourceError::KeyDerivationLimitExceeded { requested: 2000, limit: 1000 };
let msg = format!("{}", err);
assert!(msg.contains("2000"));
assert!(msg.contains("1000"));
}
}