use crate::security::authenticated::AuthenticatedSymbol;
use crate::security::error::{AuthError, AuthErrorKind};
use crate::security::key::AuthKey;
use crate::security::tag::AuthenticationTag;
use crate::types::Symbol;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthMode {
Strict,
Permissive,
Disabled,
}
#[derive(Debug, Default)]
pub struct AuthStats {
pub signed: AtomicU64,
pub verified_ok: AtomicU64,
pub verified_fail: AtomicU64,
pub failures_allowed: AtomicU64,
pub skipped: AtomicU64,
}
#[derive(Debug, Clone)]
pub struct SecurityContext {
key: AuthKey,
mode: AuthMode,
stats: Arc<AuthStats>,
}
impl SecurityContext {
#[must_use]
pub fn new(key: AuthKey) -> Self {
Self {
key,
mode: AuthMode::Strict,
stats: Arc::new(AuthStats::default()),
}
}
#[must_use]
pub fn for_testing(seed: u64) -> Self {
Self::new(AuthKey::from_seed(seed))
}
#[must_use]
pub fn with_mode(mut self, mode: AuthMode) -> Self {
self.mode = mode;
self
}
#[must_use]
pub fn sign_symbol(&self, symbol: &Symbol) -> AuthenticatedSymbol {
let tag = AuthenticationTag::compute(&self.key, symbol);
self.stats.signed.fetch_add(1, Ordering::Relaxed);
AuthenticatedSymbol::new_verified(symbol.clone(), tag)
}
pub fn verify_authenticated_symbol(
&self,
auth: &mut AuthenticatedSymbol,
) -> Result<(), AuthError> {
if self.mode == AuthMode::Disabled {
self.stats.skipped.fetch_add(1, Ordering::Relaxed);
return Ok(());
}
let is_valid = auth.tag().verify(&self.key, auth.symbol());
auth.set_verified(is_valid);
if is_valid {
self.stats.verified_ok.fetch_add(1, Ordering::Relaxed);
Ok(())
} else {
self.stats.verified_fail.fetch_add(1, Ordering::Relaxed);
match self.mode {
AuthMode::Strict => Err(AuthError::new(
AuthErrorKind::InvalidTag,
format!("symbol verification failed for {}", auth.symbol().id()),
)),
AuthMode::Permissive => {
self.stats.failures_allowed.fetch_add(1, Ordering::Relaxed);
Ok(())
}
AuthMode::Disabled => unreachable!(),
}
}
}
#[must_use]
pub fn derive_context(&self, purpose: &[u8]) -> Self {
Self {
key: self.key.derive_subkey(purpose),
mode: self.mode,
stats: Arc::new(AuthStats::default()), }
}
#[must_use]
pub fn stats(&self) -> &AuthStats {
&self.stats
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{SymbolId, SymbolKind};
#[test]
fn context_creation() {
let key = AuthKey::from_seed(42);
let ctx = SecurityContext::new(key);
assert!(matches!(ctx.mode, AuthMode::Strict));
}
#[test]
fn context_sign_and_verify() {
let ctx = SecurityContext::for_testing(123);
let id = SymbolId::new_for_test(1, 0, 0);
let symbol = Symbol::new(id, vec![1, 2, 3], SymbolKind::Source);
let auth = ctx.sign_symbol(&symbol);
assert!(auth.is_verified());
let mut received = AuthenticatedSymbol::from_parts(auth.clone().into_symbol(), *auth.tag());
assert!(!received.is_verified());
ctx.verify_authenticated_symbol(&mut received)
.expect("verification failed");
assert!(received.is_verified());
}
#[test]
fn disabled_mode_skips_verification() {
let ctx = SecurityContext::for_testing(123).with_mode(AuthMode::Disabled);
let id = SymbolId::new_for_test(1, 0, 0);
let symbol = Symbol::new(id, vec![1, 2, 3], SymbolKind::Source);
let tag = AuthenticationTag::zero();
let mut auth = AuthenticatedSymbol::from_parts(symbol, tag);
ctx.verify_authenticated_symbol(&mut auth)
.expect("disabled mode should not error");
assert!(!auth.is_verified()); assert_eq!(ctx.stats().skipped.load(Ordering::Relaxed), 1);
}
#[test]
fn strict_mode_fails_verification() {
let ctx = SecurityContext::for_testing(123).with_mode(AuthMode::Strict);
let id = SymbolId::new_for_test(1, 0, 0);
let symbol = Symbol::new(id, vec![1, 2, 3], SymbolKind::Source);
let tag = AuthenticationTag::zero();
let mut auth = AuthenticatedSymbol::from_parts(symbol, tag);
let result = ctx.verify_authenticated_symbol(&mut auth);
assert!(result.is_err());
assert!(result.unwrap_err().is_invalid_tag());
assert!(!auth.is_verified());
assert_eq!(ctx.stats().verified_fail.load(Ordering::Relaxed), 1);
}
#[test]
fn permissive_mode_allows_failures() {
let ctx = SecurityContext::for_testing(123).with_mode(AuthMode::Permissive);
let id = SymbolId::new_for_test(1, 0, 0);
let symbol = Symbol::new(id, vec![1, 2, 3], SymbolKind::Source);
let tag = AuthenticationTag::zero();
let mut auth = AuthenticatedSymbol::from_parts(symbol, tag);
let result = ctx.verify_authenticated_symbol(&mut auth);
assert!(result.is_ok());
assert!(!auth.is_verified());
assert_eq!(ctx.stats().verified_fail.load(Ordering::Relaxed), 1);
assert_eq!(ctx.stats().failures_allowed.load(Ordering::Relaxed), 1);
}
#[test]
fn strict_mode_clears_preverified_flag_on_failure() {
let signing_ctx = SecurityContext::for_testing(123);
let verifying_ctx = SecurityContext::for_testing(456).with_mode(AuthMode::Strict);
let id = SymbolId::new_for_test(1, 0, 0);
let symbol = Symbol::new(id, vec![1, 2, 3], SymbolKind::Source);
let mut auth = signing_ctx.sign_symbol(&symbol);
assert!(auth.is_verified());
let result = verifying_ctx.verify_authenticated_symbol(&mut auth);
assert!(result.is_err());
assert!(result.unwrap_err().is_invalid_tag());
assert!(
!auth.is_verified(),
"failed re-verification must clear any stale trusted state"
);
assert_eq!(
verifying_ctx.stats().verified_fail.load(Ordering::Relaxed),
1
);
assert_eq!(verifying_ctx.stats().verified_ok.load(Ordering::Relaxed), 0);
}
#[test]
fn permissive_mode_clears_preverified_flag_on_failure() {
let signing_ctx = SecurityContext::for_testing(123);
let verifying_ctx = SecurityContext::for_testing(456).with_mode(AuthMode::Permissive);
let id = SymbolId::new_for_test(1, 0, 0);
let symbol = Symbol::new(id, vec![1, 2, 3], SymbolKind::Source);
let mut auth = signing_ctx.sign_symbol(&symbol);
assert!(auth.is_verified());
let result = verifying_ctx.verify_authenticated_symbol(&mut auth);
assert!(result.is_ok());
assert!(
!auth.is_verified(),
"permissive mode may allow the symbol through, but it must not preserve a stale verified flag"
);
assert_eq!(
verifying_ctx.stats().verified_fail.load(Ordering::Relaxed),
1
);
assert_eq!(
verifying_ctx
.stats()
.failures_allowed
.load(Ordering::Relaxed),
1
);
}
#[test]
fn accessor_delegation() {
let ctx = SecurityContext::for_testing(123);
let id = SymbolId::new_for_test(1, 0, 0);
let symbol = Symbol::new(id, vec![1, 2, 3], SymbolKind::Source);
let auth = ctx.sign_symbol(&symbol);
assert_eq!(ctx.stats().signed.load(Ordering::Relaxed), 1);
assert_eq!(auth.symbol(), &symbol);
}
#[test]
fn cross_context_verification_isolation() {
let primary = SecurityContext::for_testing(99);
let transport_ctx = primary.derive_context(b"transport");
let storage_ctx = primary.derive_context(b"storage");
let id = SymbolId::new_for_test(1, 0, 0);
let symbol = Symbol::new(id, vec![10, 20, 30], SymbolKind::Source);
let auth = transport_ctx.sign_symbol(&symbol);
assert!(auth.is_verified());
let mut received = AuthenticatedSymbol::from_parts(auth.clone().into_symbol(), *auth.tag());
let result = storage_ctx.verify_authenticated_symbol(&mut received);
assert!(result.is_err());
assert!(result.unwrap_err().is_invalid_tag());
assert!(!received.is_verified());
let mut received2 =
AuthenticatedSymbol::from_parts(auth.clone().into_symbol(), *auth.tag());
transport_ctx
.verify_authenticated_symbol(&mut received2)
.expect("same context verification must succeed");
assert!(received2.is_verified());
}
#[test]
fn permissive_mode_with_valid_tag_marks_verified() {
let ctx = SecurityContext::for_testing(42).with_mode(AuthMode::Permissive);
let id = SymbolId::new_for_test(1, 0, 0);
let symbol = Symbol::new(id, vec![5, 6, 7], SymbolKind::Source);
let auth = ctx.sign_symbol(&symbol);
let mut received = AuthenticatedSymbol::from_parts(auth.clone().into_symbol(), *auth.tag());
assert!(!received.is_verified());
ctx.verify_authenticated_symbol(&mut received)
.expect("valid tag in permissive mode should succeed");
assert!(received.is_verified());
assert_eq!(ctx.stats().verified_ok.load(Ordering::Relaxed), 1);
assert_eq!(ctx.stats().failures_allowed.load(Ordering::Relaxed), 0);
}
#[test]
fn disabled_mode_preserves_pre_verified_flag() {
let signing_ctx = SecurityContext::for_testing(42);
let disabled_ctx = SecurityContext::for_testing(42).with_mode(AuthMode::Disabled);
let id = SymbolId::new_for_test(1, 0, 0);
let symbol = Symbol::new(id, vec![1, 2], SymbolKind::Source);
let mut auth = signing_ctx.sign_symbol(&symbol);
assert!(auth.is_verified());
disabled_ctx
.verify_authenticated_symbol(&mut auth)
.expect("disabled mode never errors");
assert!(
auth.is_verified(),
"disabled mode must not clear pre-existing verified flag"
);
}
#[test]
fn cloned_context_shares_stats() {
let ctx1 = SecurityContext::for_testing(77);
let ctx2 = ctx1.clone();
let id = SymbolId::new_for_test(1, 0, 0);
let symbol = Symbol::new(id, vec![1], SymbolKind::Source);
let _ = ctx1.sign_symbol(&symbol);
let _ = ctx2.sign_symbol(&symbol);
assert_eq!(ctx1.stats().signed.load(Ordering::Relaxed), 2);
assert_eq!(ctx2.stats().signed.load(Ordering::Relaxed), 2);
}
#[test]
fn derived_context_has_independent_stats() {
let primary = SecurityContext::for_testing(42);
let derived = primary.derive_context(b"child");
let id = SymbolId::new_for_test(1, 0, 0);
let symbol = Symbol::new(id, vec![1], SymbolKind::Source);
let _ = primary.sign_symbol(&symbol);
assert_eq!(primary.stats().signed.load(Ordering::Relaxed), 1);
assert_eq!(
derived.stats().signed.load(Ordering::Relaxed),
0,
"derived context must have independent stats"
);
}
}