use serde::{Deserialize, Serialize};
use crate::error::{IdentityError, Result};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrustConstraints {
pub not_before: u64,
pub not_after: Option<u64>,
pub max_uses: Option<u64>,
pub geographic: Option<Vec<String>>,
pub ip_allowlist: Option<Vec<String>>,
pub custom: Option<serde_json::Value>,
}
impl TrustConstraints {
pub fn open() -> Self {
Self {
not_before: crate::time::now_micros(),
not_after: None,
max_uses: None,
geographic: None,
ip_allowlist: None,
custom: None,
}
}
pub fn time_bounded(not_before: u64, not_after: u64) -> Self {
Self {
not_before,
not_after: Some(not_after),
max_uses: None,
geographic: None,
ip_allowlist: None,
custom: None,
}
}
pub fn with_max_uses(mut self, max: u64) -> Self {
self.max_uses = Some(max);
self
}
pub fn validate(&self, now: u64, current_uses: u64) -> Result<()> {
if now < self.not_before {
return Err(IdentityError::TrustNotYetValid);
}
if let Some(expiry) = self.not_after {
if now > expiry {
return Err(IdentityError::TrustExpired);
}
}
if let Some(max) = self.max_uses {
if current_uses >= max {
return Err(IdentityError::MaxUsesExceeded);
}
}
Ok(())
}
pub fn is_time_valid(&self, now: u64) -> bool {
if now < self.not_before {
return false;
}
if let Some(expiry) = self.not_after {
if now > expiry {
return false;
}
}
true
}
pub fn is_within_uses(&self, current_uses: u64) -> bool {
match self.max_uses {
Some(max) => current_uses < max,
None => true,
}
}
}
impl Default for TrustConstraints {
fn default() -> Self {
Self::open()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_open_constraints() {
let c = TrustConstraints::open();
let now = crate::time::now_micros();
assert!(c.validate(now, 0).is_ok());
assert!(c.is_time_valid(now));
assert!(c.is_within_uses(999));
}
#[test]
fn test_time_bounded_valid() {
let now = crate::time::now_micros();
let c = TrustConstraints::time_bounded(now - 1_000_000, now + 1_000_000);
assert!(c.validate(now, 0).is_ok());
}
#[test]
fn test_time_bounded_expired() {
let now = crate::time::now_micros();
let c = TrustConstraints::time_bounded(now - 2_000_000, now - 1_000_000);
let result = c.validate(now, 0);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), IdentityError::TrustExpired));
}
#[test]
fn test_time_bounded_not_yet_valid() {
let now = crate::time::now_micros();
let c = TrustConstraints::time_bounded(now + 1_000_000, now + 2_000_000);
let result = c.validate(now, 0);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
IdentityError::TrustNotYetValid
));
}
#[test]
fn test_max_uses() {
let c = TrustConstraints::open().with_max_uses(3);
let now = crate::time::now_micros();
assert!(c.validate(now, 0).is_ok());
assert!(c.validate(now, 1).is_ok());
assert!(c.validate(now, 2).is_ok());
let result = c.validate(now, 3);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
IdentityError::MaxUsesExceeded
));
}
#[test]
fn test_is_within_uses() {
let c = TrustConstraints::open().with_max_uses(5);
assert!(c.is_within_uses(0));
assert!(c.is_within_uses(4));
assert!(!c.is_within_uses(5));
assert!(!c.is_within_uses(10));
}
#[test]
fn test_unlimited_uses() {
let c = TrustConstraints::open();
assert!(c.is_within_uses(u64::MAX));
}
}