use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::sync::atomic::{AtomicU64, Ordering};
use tracing::info;
use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
use uuid::Uuid;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum KeyType {
Symmetric,
AsymmetricPublic,
AsymmetricPrivate,
KeyPair,
}
impl fmt::Display for KeyType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
KeyType::Symmetric => write!(f, "Symmetric"),
KeyType::AsymmetricPublic => write!(f, "AsymmetricPublic"),
KeyType::AsymmetricPrivate => write!(f, "AsymmetricPrivate"),
KeyType::KeyPair => write!(f, "KeyPair"),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum KeyPurpose {
Encryption,
Signing,
KeyExchange,
Authentication,
KeyWrapping,
}
impl fmt::Display for KeyPurpose {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
KeyPurpose::Encryption => write!(f, "Encryption"),
KeyPurpose::Signing => write!(f, "Signing"),
KeyPurpose::KeyExchange => write!(f, "KeyExchange"),
KeyPurpose::Authentication => write!(f, "Authentication"),
KeyPurpose::KeyWrapping => write!(f, "KeyWrapping"),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RotationReason {
Scheduled,
Compromised,
PolicyChange,
Expiration,
Manual,
}
impl fmt::Display for RotationReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RotationReason::Scheduled => write!(f, "Scheduled"),
RotationReason::Compromised => write!(f, "Compromised"),
RotationReason::PolicyChange => write!(f, "PolicyChange"),
RotationReason::Expiration => write!(f, "Expiration"),
RotationReason::Manual => write!(f, "Manual"),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DestructionMethod {
Zeroization,
CryptoErase,
Manual,
}
impl fmt::Display for DestructionMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DestructionMethod::Zeroization => write!(f, "Zeroization"),
DestructionMethod::CryptoErase => write!(f, "CryptoErase"),
DestructionMethod::Manual => write!(f, "Manual"),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum KeyLifecycleEvent {
Generated {
key_id: String,
algorithm: String,
key_type: KeyType,
purpose: KeyPurpose,
},
Rotated {
old_key_id: String,
new_key_id: String,
algorithm: String,
reason: RotationReason,
},
Deprecated {
key_id: String,
deprecation_date: DateTime<Utc>,
reason: String,
},
Suspended {
key_id: String,
reason: String,
},
Destroyed {
key_id: String,
method: DestructionMethod,
},
Accessed {
key_id: String,
operation: String,
accessor: Option<String>,
},
Exported {
key_id: String,
format: String,
destination: String,
},
Imported {
key_id: String,
algorithm: String,
source: String,
},
}
impl KeyLifecycleEvent {
#[must_use]
pub fn key_id(&self) -> &str {
match self {
KeyLifecycleEvent::Generated { key_id, .. }
| KeyLifecycleEvent::Deprecated { key_id, .. }
| KeyLifecycleEvent::Suspended { key_id, .. }
| KeyLifecycleEvent::Destroyed { key_id, .. }
| KeyLifecycleEvent::Accessed { key_id, .. }
| KeyLifecycleEvent::Exported { key_id, .. }
| KeyLifecycleEvent::Imported { key_id, .. } => key_id,
KeyLifecycleEvent::Rotated { new_key_id, .. } => new_key_id,
}
}
#[must_use]
pub fn event_type(&self) -> &'static str {
match self {
KeyLifecycleEvent::Generated { .. } => "generated",
KeyLifecycleEvent::Rotated { .. } => "rotated",
KeyLifecycleEvent::Deprecated { .. } => "deprecated",
KeyLifecycleEvent::Suspended { .. } => "suspended",
KeyLifecycleEvent::Destroyed { .. } => "destroyed",
KeyLifecycleEvent::Accessed { .. } => "accessed",
KeyLifecycleEvent::Exported { .. } => "exported",
KeyLifecycleEvent::Imported { .. } => "imported",
}
}
#[must_use]
pub fn generated(
key_id: impl Into<String>,
algorithm: impl Into<String>,
key_type: KeyType,
purpose: KeyPurpose,
) -> Self {
Self::Generated { key_id: key_id.into(), algorithm: algorithm.into(), key_type, purpose }
}
#[must_use]
pub fn rotated(
old_key_id: impl Into<String>,
new_key_id: impl Into<String>,
algorithm: impl Into<String>,
reason: RotationReason,
) -> Self {
Self::Rotated {
old_key_id: old_key_id.into(),
new_key_id: new_key_id.into(),
algorithm: algorithm.into(),
reason,
}
}
#[must_use]
pub fn destroyed(key_id: impl Into<String>, method: DestructionMethod) -> Self {
Self::Destroyed { key_id: key_id.into(), method }
}
#[must_use]
pub fn accessed(
key_id: impl Into<String>,
operation: impl Into<String>,
accessor: Option<String>,
) -> Self {
Self::Accessed { key_id: key_id.into(), operation: operation.into(), accessor }
}
#[must_use]
pub fn imported(
key_id: impl Into<String>,
algorithm: impl Into<String>,
source: impl Into<String>,
) -> Self {
Self::Imported { key_id: key_id.into(), algorithm: algorithm.into(), source: source.into() }
}
#[must_use]
pub fn exported(
key_id: impl Into<String>,
format: impl Into<String>,
destination: impl Into<String>,
) -> Self {
Self::Exported {
key_id: key_id.into(),
format: format.into(),
destination: destination.into(),
}
}
#[must_use]
pub fn deprecated(
key_id: impl Into<String>,
deprecation_date: DateTime<Utc>,
reason: impl Into<String>,
) -> Self {
Self::Deprecated { key_id: key_id.into(), deprecation_date, reason: reason.into() }
}
#[must_use]
pub fn suspended(key_id: impl Into<String>, reason: impl Into<String>) -> Self {
Self::Suspended { key_id: key_id.into(), reason: reason.into() }
}
}
thread_local! {
static CORRELATION_ID: RefCell<Option<String>> = const { RefCell::new(None) };
}
static CORRELATION_COUNTER: AtomicU64 = AtomicU64::new(0);
#[must_use]
pub fn generate_correlation_id() -> String {
Uuid::new_v4().to_string()
}
#[must_use]
pub fn generate_lightweight_correlation_id() -> String {
let counter = CORRELATION_COUNTER.fetch_add(1, Ordering::SeqCst);
format!("corr-{counter:016x}")
}
pub fn set_correlation_id(id: impl Into<String>) {
CORRELATION_ID.with(|cell| {
*cell.borrow_mut() = Some(id.into());
});
}
#[must_use]
pub fn current_correlation_id() -> Option<String> {
CORRELATION_ID.with(|cell| cell.borrow().clone())
}
pub fn clear_correlation_id() {
CORRELATION_ID.with(|cell| {
*cell.borrow_mut() = None;
});
}
pub fn with_correlation_id<F, R>(id: String, f: F) -> R
where
F: FnOnce() -> R,
{
let old = current_correlation_id();
set_correlation_id(id);
let result = f();
match old {
Some(prev_id) => set_correlation_id(prev_id),
None => clear_correlation_id(),
}
result
}
pub struct CorrelationGuard {
previous: Option<String>,
}
impl CorrelationGuard {
#[must_use]
pub fn new() -> Self {
let previous = current_correlation_id();
set_correlation_id(generate_correlation_id());
Self { previous }
}
#[must_use]
pub fn with_id(id: impl Into<String>) -> Self {
let previous = current_correlation_id();
set_correlation_id(id);
Self { previous }
}
#[must_use]
pub fn lightweight() -> Self {
let previous = current_correlation_id();
set_correlation_id(generate_lightweight_correlation_id());
Self { previous }
}
#[must_use]
pub fn id(&self) -> Option<String> {
current_correlation_id()
}
#[must_use]
pub fn previous_id(&self) -> Option<&String> {
self.previous.as_ref()
}
}
impl Drop for CorrelationGuard {
fn drop(&mut self) {
match &self.previous {
Some(id) => set_correlation_id(id.clone()),
None => clear_correlation_id(),
}
}
}
impl Default for CorrelationGuard {
fn default() -> Self {
Self::new()
}
}
pub const SENSITIVE_KEY_PATTERNS: &[&str] = &[
"key",
"secret",
"password",
"token",
"credential",
"private",
"auth",
"session",
"bearer",
"api_key",
"apikey",
"passphrase",
];
pub const MAX_METADATA_VALUE_LENGTH: usize = 1000;
#[must_use]
pub fn is_potentially_sensitive(key: &str) -> bool {
let lower = key.to_lowercase();
SENSITIVE_KEY_PATTERNS.iter().any(|pattern| lower.contains(pattern))
}
#[must_use]
pub fn sanitize_value(key: &str, value: &str) -> String {
if is_potentially_sensitive(key) {
"[REDACTED]".to_string()
} else if value.len() > MAX_METADATA_VALUE_LENGTH {
format!("[{} chars truncated]", value.len())
} else {
value.to_string()
}
}
fn sha256_first_16_hex(data: &[u8]) -> String {
let Ok(result) = crate::primitives::hash::sha2::sha256(data) else {
return String::from("hash-error");
};
result.get(..8).map_or_else(|| hex::encode(result), hex::encode)
}
#[must_use]
pub fn sanitize_bytes(data: &[u8]) -> String {
if data.len() <= 32 {
format!("[{} bytes]", data.len())
} else {
let fingerprint = sha256_first_16_hex(data);
format!("[{} bytes, fingerprint: {}]", data.len(), fingerprint)
}
}
#[must_use]
pub fn sanitize_metadata(metadata: &HashMap<String, String>) -> HashMap<String, String> {
metadata.iter().map(|(k, v)| (k.clone(), sanitize_value(k, v))).collect()
}
pub fn init_tracing() -> Result<(), Box<dyn std::error::Error>> {
let filter =
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("latticearc=info"));
let subscriber = tracing_subscriber::registry().with(filter).with(
tracing_subscriber::fmt::layer()
.with_target(false)
.with_thread_ids(false)
.with_thread_names(false)
.compact(),
);
subscriber.init();
info!("LatticeArc logging initialized");
Ok(())
}
#[must_use]
pub fn sanitize_data(data: &[u8]) -> SanitizedData<'_> {
SanitizedData(data)
}
pub struct SanitizedData<'a>(&'a [u8]);
impl<'a> fmt::Display for SanitizedData<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0.len() <= 32 {
write!(f, "[{} bytes]", self.0.len())
} else {
let hash = blake2_hash(self.0);
write!(f, "[{} bytes, hash: {}]", self.0.len(), &hash[..16])
}
}
}
fn blake2_hash(data: &[u8]) -> String {
hex::encode(crate::primitives::hash::blake2::blake2s_256(data))
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_crypto_start {
($operation:expr) => {
trace!("Starting crypto operation: {}", $operation);
};
($operation:expr, $($field:tt)*) => {
trace!("Starting crypto operation: {} {}", $operation, format_args!($($field)*));
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_crypto_complete {
($operation:expr) => {
trace!("Completed crypto operation: {}", $operation);
};
($operation:expr, $($field:tt)*) => {
trace!("Completed crypto operation: {} {}", $operation, format_args!($($field)*));
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_crypto_error {
($operation:expr, $error:expr) => {
error!("Crypto operation failed: {} - {}", $operation, $error);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_key_generation {
($algorithm:expr, $key_type:expr) => {
info!("Generated {} key using {}", $key_type, $algorithm);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_encryption {
($algorithm:expr, $data_len:expr) => {
debug!("Encrypted {} bytes using {}", $data_len, $algorithm);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_decryption {
($algorithm:expr, $data_len:expr) => {
debug!("Decrypted {} bytes using {}", $data_len, $algorithm);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_signature {
($algorithm:expr) => {
debug!("Created signature using {}", $algorithm);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_verification {
($algorithm:expr, $result:expr) => {
debug!(
"Verification {} using {}",
if $result { "succeeded" } else { "failed" },
$algorithm
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_security_event {
($event:expr) => {
tracing::event!(Level::ERROR, security_event = true, "{}", $event);
};
($event:expr, $($field:tt)*) => {
tracing::event!(Level::ERROR, security_event = true, "{} {}", $event, format_args!($($field)*));
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_performance {
($operation:expr, $duration:expr) => {
debug!("Performance: {} took {:?}", $operation, $duration);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_key_generated {
($key_id:expr, $algorithm:expr, $key_type:expr, $purpose:expr) => {
tracing::info!(
target: "key_lifecycle::generated",
key_id = %$key_id,
algorithm = %$algorithm,
key_type = ?$key_type,
purpose = ?$purpose,
"Key generated"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_key_rotated {
($old_key_id:expr, $new_key_id:expr, $algorithm:expr, $reason:expr) => {
tracing::warn!(
target: "key_lifecycle::rotated",
old_key_id = %$old_key_id,
new_key_id = %$new_key_id,
algorithm = %$algorithm,
reason = ?$reason,
"Key rotated"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_key_destroyed {
($key_id:expr, $method:expr) => {
tracing::warn!(
target: "key_lifecycle::destroyed",
key_id = %$key_id,
method = ?$method,
"Key destroyed"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_key_accessed {
($key_id:expr, $operation:expr) => {
tracing::debug!(
target: "key_lifecycle::accessed",
key_id = %$key_id,
operation = %$operation,
"Key accessed"
);
};
($key_id:expr, $operation:expr, $accessor:expr) => {
tracing::debug!(
target: "key_lifecycle::accessed",
key_id = %$key_id,
operation = %$operation,
accessor = %$accessor,
"Key accessed"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_key_deprecated {
($key_id:expr, $reason:expr) => {
tracing::info!(
target: "key_lifecycle::deprecated",
key_id = %$key_id,
reason = %$reason,
"Key deprecated"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_key_suspended {
($key_id:expr, $reason:expr) => {
tracing::warn!(
target: "key_lifecycle::suspended",
key_id = %$key_id,
reason = %$reason,
"Key suspended"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_key_exported {
($key_id:expr, $format:expr, $destination:expr) => {
tracing::warn!(
target: "key_lifecycle::exported",
key_id = %$key_id,
format = %$format,
destination = %$destination,
"Key exported"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_key_imported {
($key_id:expr, $algorithm:expr, $source:expr) => {
tracing::info!(
target: "key_lifecycle::imported",
key_id = %$key_id,
algorithm = %$algorithm,
source = %$source,
"Key imported"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_crypto_operation {
($op:expr, $($field:tt)*) => {
if let Some(corr_id) = $crate::unified_api::logging::current_correlation_id() {
tracing::debug!(
target: "crypto::operation",
correlation_id = %corr_id,
operation = $op,
$($field)*
);
} else {
tracing::debug!(
target: "crypto::operation",
operation = $op,
$($field)*
);
}
};
($op:expr) => {
if let Some(corr_id) = $crate::unified_api::logging::current_correlation_id() {
tracing::debug!(
target: "crypto::operation",
correlation_id = %corr_id,
operation = $op,
);
} else {
tracing::debug!(
target: "crypto::operation",
operation = $op,
);
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_crypto_operation_start {
($op:expr, $($field:tt)*) => {
if let Some(corr_id) = $crate::unified_api::logging::current_correlation_id() {
tracing::trace!(
target: "crypto::operation",
correlation_id = %corr_id,
operation = $op,
phase = "start",
$($field)*
);
} else {
tracing::trace!(
target: "crypto::operation",
operation = $op,
phase = "start",
$($field)*
);
}
};
($op:expr) => {
if let Some(corr_id) = $crate::unified_api::logging::current_correlation_id() {
tracing::trace!(
target: "crypto::operation",
correlation_id = %corr_id,
operation = $op,
phase = "start",
);
} else {
tracing::trace!(
target: "crypto::operation",
operation = $op,
phase = "start",
);
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_crypto_operation_complete {
($op:expr, $($field:tt)*) => {
if let Some(corr_id) = $crate::unified_api::logging::current_correlation_id() {
tracing::trace!(
target: "crypto::operation",
correlation_id = %corr_id,
operation = $op,
phase = "complete",
$($field)*
);
} else {
tracing::trace!(
target: "crypto::operation",
operation = $op,
phase = "complete",
$($field)*
);
}
};
($op:expr) => {
if let Some(corr_id) = $crate::unified_api::logging::current_correlation_id() {
tracing::trace!(
target: "crypto::operation",
correlation_id = %corr_id,
operation = $op,
phase = "complete",
);
} else {
tracing::trace!(
target: "crypto::operation",
operation = $op,
phase = "complete",
);
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_crypto_operation_error {
($op:expr, $error:expr, $($field:tt)*) => {
if let Some(corr_id) = $crate::unified_api::logging::current_correlation_id() {
tracing::error!(
target: "crypto::operation",
correlation_id = %corr_id,
operation = $op,
error = %$error,
phase = "error",
$($field)*
);
} else {
tracing::error!(
target: "crypto::operation",
operation = $op,
error = %$error,
phase = "error",
$($field)*
);
}
};
($op:expr, $error:expr) => {
if let Some(corr_id) = $crate::unified_api::logging::current_correlation_id() {
tracing::error!(
target: "crypto::operation",
correlation_id = %corr_id,
operation = $op,
error = %$error,
phase = "error",
);
} else {
tracing::error!(
target: "crypto::operation",
operation = $op,
error = %$error,
phase = "error",
);
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_zero_trust_auth_attempt {
($session_id_hex:expr) => {
tracing::info!(
target: "zero_trust::auth",
session_id = %$session_id_hex,
"Zero Trust authentication attempt initiated"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_zero_trust_auth_success {
($session_id_hex:expr, $trust_level:expr) => {
tracing::info!(
target: "zero_trust::auth",
session_id = %$session_id_hex,
trust_level = ?$trust_level,
"Zero Trust authentication succeeded"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_zero_trust_auth_failure {
($session_id_hex:expr, $reason:expr) => {
tracing::error!(
target: "zero_trust::auth",
session_id = %$session_id_hex,
reason = %$reason,
"Zero Trust authentication failed"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_zero_trust_session_created {
($session_id_hex:expr, $trust_level:expr, $expires_at:expr) => {
tracing::info!(
target: "zero_trust::session",
session_id = %$session_id_hex,
trust_level = ?$trust_level,
expires_at = %$expires_at,
"Zero Trust session created"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_zero_trust_session_verified {
($session_id_hex:expr) => {
tracing::info!(
target: "zero_trust::session",
session_id = %$session_id_hex,
"Zero Trust session verified"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_zero_trust_session_verification_failed {
($session_id_hex:expr, $reason:expr) => {
tracing::error!(
target: "zero_trust::session",
session_id = %$session_id_hex,
reason = %$reason,
"Zero Trust session verification failed"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_zero_trust_session_expired {
($session_id_hex:expr) => {
tracing::warn!(
target: "zero_trust::session",
session_id = %$session_id_hex,
"Zero Trust session expired"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_zero_trust_trust_level_changed {
($session_id_hex:expr, $from_level:expr, $to_level:expr) => {
tracing::info!(
target: "zero_trust::trust",
session_id = %$session_id_hex,
from_level = ?$from_level,
to_level = ?$to_level,
"Zero Trust trust level changed"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_zero_trust_unverified_mode {
($operation:expr) => {
tracing::warn!(
target: "zero_trust::security",
operation = %$operation,
"Operation performed in SecurityMode::Unverified - no session verification"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_zero_trust_challenge_generated {
($complexity:expr) => {
tracing::debug!(
target: "zero_trust::auth",
complexity = ?$complexity,
"Zero Trust challenge generated"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_zero_trust_proof_verified {
($result:expr) => {
if $result {
tracing::debug!(
target: "zero_trust::auth",
"Zero Trust proof verification succeeded"
);
} else {
tracing::warn!(
target: "zero_trust::auth",
"Zero Trust proof verification failed"
);
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_zero_trust_access_decision {
($session_id_hex:expr, $allowed:expr, $reason:expr) => {
if $allowed {
tracing::info!(
target: "zero_trust::access",
session_id = %$session_id_hex,
allowed = $allowed,
reason = %$reason,
"Zero Trust access granted"
);
} else {
tracing::warn!(
target: "zero_trust::access",
session_id = %$session_id_hex,
allowed = $allowed,
reason = %$reason,
"Zero Trust access denied"
);
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_zero_trust_session_revoked {
($session_id_hex:expr) => {
tracing::info!(
target: "zero_trust::session",
session_id = %$session_id_hex,
"Zero Trust session revoked"
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_zero_trust_continuous_verification {
($session_id_hex:expr, $status:expr) => {
tracing::debug!(
target: "zero_trust::session",
session_id = %$session_id_hex,
status = ?$status,
"Zero Trust continuous verification status"
);
};
}
#[must_use]
pub fn session_id_to_hex(session_id: &[u8]) -> String {
hex::encode(session_id)
}
pub fn init_tracing_with_file(log_file: &str) -> Result<(), Box<dyn std::error::Error>> {
use tracing_appender::rolling::{RollingFileAppender, Rotation};
let file_appender = RollingFileAppender::new(Rotation::DAILY, "logs", log_file);
let filter =
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("latticearc=info"));
let subscriber = tracing_subscriber::registry()
.with(filter)
.with(tracing_subscriber::fmt::layer().with_target(false).with_writer(file_appender));
subscriber.init();
info!("LatticeArc logging to file initialized: {}", log_file);
Ok(())
}
#[cfg(test)]
#[allow(
clippy::panic,
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::arithmetic_side_effects,
clippy::panic_in_result_fn,
clippy::unnecessary_wraps,
clippy::redundant_clone,
clippy::useless_vec,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::clone_on_copy,
clippy::len_zero,
clippy::single_match,
clippy::unnested_or_patterns,
clippy::default_constructed_unit_structs,
clippy::redundant_closure_for_method_calls,
clippy::semicolon_if_nothing_returned,
clippy::unnecessary_unwrap,
clippy::redundant_pattern_matching,
clippy::missing_const_for_thread_local,
clippy::get_first,
clippy::float_cmp,
clippy::needless_borrows_for_generic_args,
clippy::unnecessary_map_or,
clippy::option_as_ref_deref,
unused_qualifications
)]
mod tests {
use super::*;
#[test]
fn test_sanitize_data_small_has_correct_format() {
let data = b"short";
let sanitized = sanitize_data(data);
assert_eq!(format!("{}", sanitized), "[5 bytes]");
}
#[test]
fn test_sanitize_data_large_has_correct_format() {
let data = vec![0u8; 100];
let sanitized = sanitize_data(&data);
let output = format!("{}", sanitized);
assert!(output.contains("[100 bytes"));
assert!(output.contains("hash:"));
}
#[test]
fn test_blake2_hash_has_correct_format() {
let data = b"test data";
let hash = blake2_hash(data);
assert_eq!(hash.len(), 64); assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn test_is_potentially_sensitive_matches_sensitive_keys_succeeds() {
assert!(is_potentially_sensitive("key"));
assert!(is_potentially_sensitive("secret"));
assert!(is_potentially_sensitive("password"));
assert!(is_potentially_sensitive("token"));
assert!(is_potentially_sensitive("credential"));
assert!(is_potentially_sensitive("private"));
assert!(is_potentially_sensitive("auth"));
assert!(is_potentially_sensitive("session"));
assert!(is_potentially_sensitive("bearer"));
assert!(is_potentially_sensitive("api_key"));
assert!(is_potentially_sensitive("apikey"));
assert!(is_potentially_sensitive("passphrase"));
}
#[test]
fn test_is_potentially_sensitive_matches_compound_keys_succeeds() {
assert!(is_potentially_sensitive("user_password"));
assert!(is_potentially_sensitive("api_key_header"));
assert!(is_potentially_sensitive("auth_token"));
assert!(is_potentially_sensitive("private_key_pem"));
assert!(is_potentially_sensitive("session_id"));
assert!(is_potentially_sensitive("bearer_token"));
assert!(is_potentially_sensitive("encryption_key"));
assert!(is_potentially_sensitive("secret_value"));
assert!(is_potentially_sensitive("credential_store"));
}
#[test]
fn test_is_potentially_sensitive_case_insensitive_succeeds() {
assert!(is_potentially_sensitive("API_KEY"));
assert!(is_potentially_sensitive("Password"));
assert!(is_potentially_sensitive("SECRET"));
assert!(is_potentially_sensitive("AuthToken"));
assert!(is_potentially_sensitive("BEARER_TOKEN"));
assert!(is_potentially_sensitive("PrivateKey"));
}
#[test]
fn test_is_potentially_sensitive_non_sensitive_keys_returns_false_succeeds() {
assert!(!is_potentially_sensitive("username"));
assert!(!is_potentially_sensitive("operation"));
assert!(!is_potentially_sensitive("timestamp"));
assert!(!is_potentially_sensitive("request_id"));
assert!(!is_potentially_sensitive("user_id"));
assert!(!is_potentially_sensitive("algorithm"));
assert!(!is_potentially_sensitive("data_size"));
assert!(!is_potentially_sensitive("operation_type"));
assert!(!is_potentially_sensitive("status"));
assert!(!is_potentially_sensitive("result"));
}
#[test]
fn test_sanitize_value_redacts_sensitive_keys_succeeds() {
assert_eq!(sanitize_value("api_key", "sk-12345abcdef"), "[REDACTED]");
assert_eq!(sanitize_value("password", "super_secret_123"), "[REDACTED]");
assert_eq!(sanitize_value("auth_token", "Bearer xyz"), "[REDACTED]");
assert_eq!(sanitize_value("private_key", "-----BEGIN PRIVATE KEY-----"), "[REDACTED]");
}
#[test]
fn test_sanitize_value_passes_normal_values_unchanged_succeeds() {
assert_eq!(sanitize_value("operation", "encrypt"), "encrypt");
assert_eq!(sanitize_value("algorithm", "ML-KEM-768"), "ML-KEM-768");
assert_eq!(sanitize_value("user_id", "user123"), "user123");
assert_eq!(sanitize_value("status", "success"), "success");
}
#[test]
fn test_sanitize_value_truncates_long_values_succeeds() {
let long_value = "x".repeat(2000);
let result = sanitize_value("data", &long_value);
assert_eq!(result, "[2000 chars truncated]");
let exact_value = "x".repeat(MAX_METADATA_VALUE_LENGTH);
let result = sanitize_value("data", &exact_value);
assert_eq!(result, exact_value);
let over_value = "x".repeat(MAX_METADATA_VALUE_LENGTH + 1);
let result = sanitize_value("data", &over_value);
assert!(result.contains("chars truncated"));
}
#[test]
fn test_sanitize_value_sensitive_key_takes_precedence_succeeds() {
let long_secret = "x".repeat(5000);
let result = sanitize_value("api_key", &long_secret);
assert_eq!(result, "[REDACTED]");
}
#[test]
fn test_sanitize_bytes_small_data_succeeds() {
assert_eq!(sanitize_bytes(&[]), "[0 bytes]");
assert_eq!(sanitize_bytes(&[1]), "[1 bytes]");
assert_eq!(sanitize_bytes(&[1, 2, 3]), "[3 bytes]");
assert_eq!(sanitize_bytes(&[0u8; 32]), "[32 bytes]");
}
#[test]
fn test_sanitize_bytes_large_data_has_fingerprint_format_has_correct_size() {
let data = vec![0u8; 33];
let result = sanitize_bytes(&data);
assert!(result.contains("33 bytes"));
assert!(result.contains("fingerprint:"));
let data = vec![0u8; 100];
let result = sanitize_bytes(&data);
assert!(result.contains("100 bytes"));
assert!(result.contains("fingerprint:"));
}
#[test]
fn test_sanitize_bytes_fingerprint_is_consistent() {
let data = b"test data for fingerprint consistency check";
let result1 = sanitize_bytes(data);
let result2 = sanitize_bytes(data);
assert_eq!(result1, result2);
}
#[test]
fn test_sanitize_bytes_fingerprint_differs_for_different_data_succeeds() {
let data1 = vec![0u8; 100];
let data2 = vec![1u8; 100];
let result1 = sanitize_bytes(&data1);
let result2 = sanitize_bytes(&data2);
assert_ne!(result1, result2);
}
#[test]
fn test_sha256_first_16_hex_has_correct_format() {
let empty_hash = sha256_first_16_hex(&[]);
assert_eq!(empty_hash.len(), 16);
assert_eq!(empty_hash, "e3b0c44298fc1c14");
assert!(empty_hash.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn test_sanitize_metadata_full_map_succeeds() {
let mut metadata = HashMap::new();
metadata.insert("operation".to_string(), "encrypt".to_string());
metadata.insert("api_key".to_string(), "sk-secret-123".to_string());
metadata.insert("user_id".to_string(), "user_456".to_string());
metadata.insert("password_hash".to_string(), "hashed_value".to_string());
metadata.insert("algorithm".to_string(), "ML-KEM-768".to_string());
let sanitized = sanitize_metadata(&metadata);
assert_eq!(sanitized.get("operation"), Some(&"encrypt".to_string()));
assert_eq!(sanitized.get("user_id"), Some(&"user_456".to_string()));
assert_eq!(sanitized.get("algorithm"), Some(&"ML-KEM-768".to_string()));
assert_eq!(sanitized.get("api_key"), Some(&"[REDACTED]".to_string()));
assert_eq!(sanitized.get("password_hash"), Some(&"[REDACTED]".to_string()));
}
#[test]
fn test_sanitize_metadata_empty_map_succeeds() {
let metadata = HashMap::new();
let sanitized = sanitize_metadata(&metadata);
assert!(sanitized.is_empty());
}
#[test]
fn test_sanitize_metadata_preserves_all_keys_succeeds() {
let mut metadata = HashMap::new();
metadata.insert("key1".to_string(), "value1".to_string());
metadata.insert("key2".to_string(), "value2".to_string());
metadata.insert("secret".to_string(), "hidden".to_string());
let sanitized = sanitize_metadata(&metadata);
assert_eq!(sanitized.len(), 3);
assert!(sanitized.contains_key("key1"));
assert!(sanitized.contains_key("key2"));
assert!(sanitized.contains_key("secret"));
}
#[test]
fn test_sanitize_metadata_with_long_values_succeeds() {
let mut metadata = HashMap::new();
let long_value = "x".repeat(2000);
metadata.insert("description".to_string(), long_value);
metadata.insert("short".to_string(), "brief".to_string());
let sanitized = sanitize_metadata(&metadata);
assert!(sanitized.get("description").map_or(false, |v| v.contains("chars truncated")));
assert_eq!(sanitized.get("short"), Some(&"brief".to_string()));
}
#[test]
fn test_session_id_to_hex_has_correct_format() {
let session_id = [0x12, 0x34, 0xAB, 0xCD];
assert_eq!(session_id_to_hex(&session_id), "1234abcd");
let empty: [u8; 0] = [];
assert_eq!(session_id_to_hex(&empty), "");
let all_zeros = [0u8; 16];
assert_eq!(session_id_to_hex(&all_zeros), "00000000000000000000000000000000");
}
#[test]
fn test_generate_correlation_id_is_valid_uuid_succeeds() {
let id = generate_correlation_id();
assert_eq!(id.len(), 36);
assert_eq!(id.chars().filter(|c| *c == '-').count(), 4);
assert!(Uuid::parse_str(&id).is_ok());
}
#[test]
fn test_generate_correlation_id_is_unique_succeeds() {
let id1 = generate_correlation_id();
let id2 = generate_correlation_id();
assert_ne!(id1, id2);
}
#[test]
fn test_generate_lightweight_correlation_id_has_correct_format() {
let id = generate_lightweight_correlation_id();
assert!(id.starts_with("corr-"));
assert_eq!(id.len(), 21);
let hex_part = &id[5..];
assert!(hex_part.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn test_generate_lightweight_correlation_id_increments_succeeds() {
let id1 = generate_lightweight_correlation_id();
let id2 = generate_lightweight_correlation_id();
assert_ne!(id1, id2);
let counter1 = u64::from_str_radix(&id1[5..], 16);
let counter2 = u64::from_str_radix(&id2[5..], 16);
assert!(counter1.is_ok());
assert!(counter2.is_ok());
}
#[test]
fn test_set_and_get_correlation_id_succeeds() {
clear_correlation_id();
assert_eq!(current_correlation_id(), None);
set_correlation_id("test-correlation-123".to_string());
assert_eq!(current_correlation_id(), Some("test-correlation-123".to_string()));
clear_correlation_id();
assert_eq!(current_correlation_id(), None);
}
#[test]
fn test_clear_correlation_id_succeeds() {
set_correlation_id("to-be-cleared".to_string());
assert!(current_correlation_id().is_some());
clear_correlation_id();
assert_eq!(current_correlation_id(), None);
}
#[test]
fn test_with_correlation_id_executes_closure_succeeds() {
clear_correlation_id();
let result = with_correlation_id("request-456".to_string(), || {
assert_eq!(current_correlation_id(), Some("request-456".to_string()));
42
});
assert_eq!(result, 42);
assert_eq!(current_correlation_id(), None);
}
#[test]
fn test_with_correlation_id_restores_previous_succeeds() {
clear_correlation_id();
set_correlation_id("outer-id".to_string());
with_correlation_id("inner-id".to_string(), || {
assert_eq!(current_correlation_id(), Some("inner-id".to_string()));
});
assert_eq!(current_correlation_id(), Some("outer-id".to_string()));
clear_correlation_id();
}
#[test]
fn test_correlation_guard_new_sets_uuid_succeeds() {
clear_correlation_id();
{
let guard = CorrelationGuard::new();
let id = guard.id();
assert!(id.is_some());
let id_str = id.as_ref().map(String::as_str);
assert!(id_str.map_or(false, |s| Uuid::parse_str(s).is_ok()));
}
assert_eq!(current_correlation_id(), None);
}
#[test]
fn test_correlation_guard_with_id_succeeds() {
clear_correlation_id();
{
let guard = CorrelationGuard::with_id("custom-id-789".to_string());
assert_eq!(guard.id(), Some("custom-id-789".to_string()));
assert_eq!(current_correlation_id(), Some("custom-id-789".to_string()));
}
assert_eq!(current_correlation_id(), None);
}
#[test]
fn test_correlation_guard_lightweight_succeeds() {
clear_correlation_id();
{
let guard = CorrelationGuard::lightweight();
let id = guard.id();
assert!(id.is_some());
assert!(id.as_ref().map_or(false, |s| s.starts_with("corr-")));
}
assert_eq!(current_correlation_id(), None);
}
#[test]
fn test_correlation_guard_restores_previous_on_drop_succeeds() {
clear_correlation_id();
set_correlation_id("original-id".to_string());
{
let guard = CorrelationGuard::with_id("temporary-id".to_string());
assert_eq!(guard.previous_id(), Some(&"original-id".to_string()));
assert_eq!(current_correlation_id(), Some("temporary-id".to_string()));
}
assert_eq!(current_correlation_id(), Some("original-id".to_string()));
clear_correlation_id();
}
#[test]
fn test_correlation_guard_nested_succeeds() {
clear_correlation_id();
{
let guard1 = CorrelationGuard::with_id("level-1".to_string());
assert_eq!(current_correlation_id(), Some("level-1".to_string()));
{
let guard2 = CorrelationGuard::with_id("level-2".to_string());
assert_eq!(current_correlation_id(), Some("level-2".to_string()));
assert_eq!(guard2.previous_id(), Some(&"level-1".to_string()));
}
assert_eq!(current_correlation_id(), Some("level-1".to_string()));
assert_eq!(guard1.previous_id(), None);
}
assert_eq!(current_correlation_id(), None);
}
#[test]
fn test_correlation_guard_default_succeeds() {
clear_correlation_id();
{
let guard = CorrelationGuard::default();
let id = guard.id();
assert!(id.is_some());
assert!(id.as_ref().map_or(false, |s| Uuid::parse_str(s).is_ok()));
}
assert_eq!(current_correlation_id(), None);
}
#[test]
fn test_key_type_display_has_correct_format() {
assert_eq!(KeyType::Symmetric.to_string(), "Symmetric");
assert_eq!(KeyType::AsymmetricPublic.to_string(), "AsymmetricPublic");
assert_eq!(KeyType::AsymmetricPrivate.to_string(), "AsymmetricPrivate");
assert_eq!(KeyType::KeyPair.to_string(), "KeyPair");
}
#[test]
fn test_key_purpose_display_has_correct_format() {
assert_eq!(KeyPurpose::Encryption.to_string(), "Encryption");
assert_eq!(KeyPurpose::Signing.to_string(), "Signing");
assert_eq!(KeyPurpose::KeyExchange.to_string(), "KeyExchange");
assert_eq!(KeyPurpose::Authentication.to_string(), "Authentication");
assert_eq!(KeyPurpose::KeyWrapping.to_string(), "KeyWrapping");
}
#[test]
fn test_rotation_reason_display_has_correct_format() {
assert_eq!(RotationReason::Scheduled.to_string(), "Scheduled");
assert_eq!(RotationReason::Compromised.to_string(), "Compromised");
assert_eq!(RotationReason::PolicyChange.to_string(), "PolicyChange");
assert_eq!(RotationReason::Expiration.to_string(), "Expiration");
assert_eq!(RotationReason::Manual.to_string(), "Manual");
}
#[test]
fn test_destruction_method_display_has_correct_format() {
assert_eq!(DestructionMethod::Zeroization.to_string(), "Zeroization");
assert_eq!(DestructionMethod::CryptoErase.to_string(), "CryptoErase");
assert_eq!(DestructionMethod::Manual.to_string(), "Manual");
}
#[test]
fn test_key_lifecycle_event_generated_has_correct_format() {
let event = KeyLifecycleEvent::generated(
"key-001",
"ML-KEM-768",
KeyType::KeyPair,
KeyPurpose::KeyExchange,
);
assert_eq!(event.key_id(), "key-001");
assert_eq!(event.event_type(), "generated");
match event {
KeyLifecycleEvent::Generated { key_id, algorithm, key_type, purpose } => {
assert_eq!(key_id, "key-001");
assert_eq!(algorithm, "ML-KEM-768");
assert_eq!(key_type, KeyType::KeyPair);
assert_eq!(purpose, KeyPurpose::KeyExchange);
}
_ => panic!("Expected Generated event"),
}
}
#[test]
fn test_key_lifecycle_event_rotated_has_correct_format() {
let event = KeyLifecycleEvent::rotated(
"old-key-001",
"new-key-002",
"ML-KEM-768",
RotationReason::Scheduled,
);
assert_eq!(event.key_id(), "new-key-002");
assert_eq!(event.event_type(), "rotated");
match event {
KeyLifecycleEvent::Rotated { old_key_id, new_key_id, algorithm, reason } => {
assert_eq!(old_key_id, "old-key-001");
assert_eq!(new_key_id, "new-key-002");
assert_eq!(algorithm, "ML-KEM-768");
assert_eq!(reason, RotationReason::Scheduled);
}
_ => panic!("Expected Rotated event"),
}
}
#[test]
fn test_key_lifecycle_event_destroyed_has_correct_format() {
let event = KeyLifecycleEvent::destroyed("key-001", DestructionMethod::Zeroization);
assert_eq!(event.key_id(), "key-001");
assert_eq!(event.event_type(), "destroyed");
match event {
KeyLifecycleEvent::Destroyed { key_id, method } => {
assert_eq!(key_id, "key-001");
assert_eq!(method, DestructionMethod::Zeroization);
}
_ => panic!("Expected Destroyed event"),
}
}
#[test]
fn test_key_lifecycle_event_accessed_has_correct_format() {
let event = KeyLifecycleEvent::accessed("key-001", "encrypt", None);
assert_eq!(event.key_id(), "key-001");
assert_eq!(event.event_type(), "accessed");
let event_with_accessor =
KeyLifecycleEvent::accessed("key-002", "sign", Some("user-123".to_string()));
match event_with_accessor {
KeyLifecycleEvent::Accessed { key_id, operation, accessor } => {
assert_eq!(key_id, "key-002");
assert_eq!(operation, "sign");
assert_eq!(accessor, Some("user-123".to_string()));
}
_ => panic!("Expected Accessed event"),
}
}
#[test]
fn test_key_lifecycle_event_imported_has_correct_format() {
let event = KeyLifecycleEvent::imported("key-001", "ML-KEM-768", "external-hsm");
assert_eq!(event.key_id(), "key-001");
assert_eq!(event.event_type(), "imported");
match event {
KeyLifecycleEvent::Imported { key_id, algorithm, source } => {
assert_eq!(key_id, "key-001");
assert_eq!(algorithm, "ML-KEM-768");
assert_eq!(source, "external-hsm");
}
_ => panic!("Expected Imported event"),
}
}
#[test]
fn test_key_lifecycle_event_exported_has_correct_format() {
let event = KeyLifecycleEvent::exported("key-001", "PEM", "backup-server");
assert_eq!(event.key_id(), "key-001");
assert_eq!(event.event_type(), "exported");
match event {
KeyLifecycleEvent::Exported { key_id, format, destination } => {
assert_eq!(key_id, "key-001");
assert_eq!(format, "PEM");
assert_eq!(destination, "backup-server");
}
_ => panic!("Expected Exported event"),
}
}
#[test]
fn test_key_lifecycle_event_deprecated_has_correct_format() {
let deprecation_date = Utc::now();
let event = KeyLifecycleEvent::deprecated("key-001", deprecation_date, "End of life");
assert_eq!(event.key_id(), "key-001");
assert_eq!(event.event_type(), "deprecated");
match event {
KeyLifecycleEvent::Deprecated { key_id, deprecation_date: date, reason } => {
assert_eq!(key_id, "key-001");
assert_eq!(date, deprecation_date);
assert_eq!(reason, "End of life");
}
_ => panic!("Expected Deprecated event"),
}
}
#[test]
fn test_key_lifecycle_event_suspended_has_correct_format() {
let event = KeyLifecycleEvent::suspended("key-001", "Suspected compromise");
assert_eq!(event.key_id(), "key-001");
assert_eq!(event.event_type(), "suspended");
match event {
KeyLifecycleEvent::Suspended { key_id, reason } => {
assert_eq!(key_id, "key-001");
assert_eq!(reason, "Suspected compromise");
}
_ => panic!("Expected Suspended event"),
}
}
#[test]
fn test_key_lifecycle_event_serializes_correctly_succeeds() {
let event = KeyLifecycleEvent::generated(
"key-001",
"ML-KEM-768",
KeyType::KeyPair,
KeyPurpose::KeyExchange,
);
let json = serde_json::to_string(&event);
assert!(json.is_ok());
let json_str = json.as_ref().map(String::as_str);
assert!(json_str.map_or(false, |s| s.contains("key-001")));
assert!(json_str.map_or(false, |s| s.contains("ML-KEM-768")));
assert!(json_str.map_or(false, |s| s.contains("KeyPair")));
assert!(json_str.map_or(false, |s| s.contains("KeyExchange")));
let deserialized: Result<KeyLifecycleEvent, _> =
serde_json::from_str(json.as_ref().map_or("", String::as_str));
assert!(deserialized.is_ok());
if let Ok(KeyLifecycleEvent::Generated { key_id, algorithm, key_type, purpose }) =
deserialized
{
assert_eq!(key_id, "key-001");
assert_eq!(algorithm, "ML-KEM-768");
assert_eq!(key_type, KeyType::KeyPair);
assert_eq!(purpose, KeyPurpose::KeyExchange);
} else {
panic!("Failed to deserialize Generated event");
}
}
#[test]
fn test_key_type_serializes_correctly_succeeds() {
for key_type in [
KeyType::Symmetric,
KeyType::AsymmetricPublic,
KeyType::AsymmetricPrivate,
KeyType::KeyPair,
] {
let json = serde_json::to_string(&key_type);
assert!(json.is_ok());
let deserialized: Result<KeyType, _> =
serde_json::from_str(json.as_ref().map_or("", String::as_str));
assert!(deserialized.is_ok());
assert_eq!(
deserialized.unwrap_or_else(|_| panic!("Failed to deserialize {:?}", key_type)),
key_type
);
}
}
#[test]
fn test_key_purpose_serializes_correctly_succeeds() {
for purpose in [
KeyPurpose::Encryption,
KeyPurpose::Signing,
KeyPurpose::KeyExchange,
KeyPurpose::Authentication,
KeyPurpose::KeyWrapping,
] {
let json = serde_json::to_string(&purpose);
assert!(json.is_ok());
let deserialized: Result<KeyPurpose, _> =
serde_json::from_str(json.as_ref().map_or("", String::as_str));
assert!(deserialized.is_ok());
assert_eq!(
deserialized.unwrap_or_else(|_| panic!("Failed to deserialize {:?}", purpose)),
purpose
);
}
}
#[test]
fn test_rotation_reason_serializes_correctly_succeeds() {
for reason in [
RotationReason::Scheduled,
RotationReason::Compromised,
RotationReason::PolicyChange,
RotationReason::Expiration,
RotationReason::Manual,
] {
let json = serde_json::to_string(&reason);
assert!(json.is_ok());
let deserialized: Result<RotationReason, _> =
serde_json::from_str(json.as_ref().map_or("", String::as_str));
assert!(deserialized.is_ok());
assert_eq!(
deserialized.unwrap_or_else(|_| panic!("Failed to deserialize {:?}", reason)),
reason
);
}
}
#[test]
fn test_destruction_method_serializes_correctly_succeeds() {
for method in [
DestructionMethod::Zeroization,
DestructionMethod::CryptoErase,
DestructionMethod::Manual,
] {
let json = serde_json::to_string(&method);
assert!(json.is_ok());
let deserialized: Result<DestructionMethod, _> =
serde_json::from_str(json.as_ref().map_or("", String::as_str));
assert!(deserialized.is_ok());
assert_eq!(
deserialized.unwrap_or_else(|_| panic!("Failed to deserialize {:?}", method)),
method
);
}
}
}