#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::undocumented_unsafe_blocks,
clippy::multiple_unsafe_ops_per_block,
clippy::arithmetic_side_effects,
clippy::print_stderr
)]
mod alloc;
mod builder;
mod error;
mod policy;
mod sys;
mod traits;
mod types;
#[cfg(feature = "serde")]
pub mod serde;
pub use builder::ShroudBuilder;
pub use error::{Result, ShroudError};
pub use policy::Policy;
pub use traits::{Expose, ExposeGuard, ExposeGuardMut, ExposeGuarded, ExposeGuardedMut, ExposeMut};
pub use types::{Shroud, ShroudedArray, ShroudedBytes, ShroudedString};
#[cfg(feature = "digest")]
pub use types::ShroudedHasher;
#[cfg(feature = "sha1")]
pub use types::ShroudedSha1;
#[cfg(feature = "sha2")]
pub use types::{ShroudedSha256, ShroudedSha384, ShroudedSha512};
pub use zeroize::Zeroize;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shrouded_bytes_basic() {
let mut data = vec![1, 2, 3, 4, 5];
let secret = ShroudedBytes::from_slice(&mut data).unwrap();
assert_eq!(secret.expose(), &[1, 2, 3, 4, 5]);
assert_eq!(data, vec![0, 0, 0, 0, 0]); }
#[test]
fn test_shrouded_string_basic() {
let secret = ShroudedString::new("password123".to_string()).unwrap();
assert_eq!(secret.expose(), "password123");
}
#[test]
fn test_shrouded_array_basic() {
let secret: ShroudedArray<16> = ShroudedArray::new_with(|buf| {
for (i, byte) in buf.iter_mut().enumerate() {
*byte = i as u8;
}
})
.unwrap();
let expected: [u8; 16] = core::array::from_fn(|i| i as u8);
assert_eq!(secret.expose(), &expected);
}
#[test]
fn test_shroud_generic() {
#[derive(zeroize::Zeroize)]
struct Key([u8; 32]);
let secret = Shroud::new(Key([0x42; 32])).unwrap();
assert_eq!(secret.expose().0[0], 0x42);
}
#[test]
fn test_debug_is_redacted() {
let secret = ShroudedString::new("super_secret".to_string()).unwrap();
let debug_output = format!("{:?}", secret);
assert!(debug_output.contains("[REDACTED]"));
assert!(!debug_output.contains("super_secret"));
}
#[test]
fn test_builder_api() {
let mut data = vec![0xAB; 16];
let secret = ShroudBuilder::new()
.policy(Policy::BestEffort)
.build_bytes(&mut data)
.unwrap();
assert_eq!(secret.len(), 16);
assert_eq!(secret.expose()[0], 0xAB);
}
#[test]
fn test_policy_disabled() {
let mut data = vec![1, 2, 3];
let secret = ShroudBuilder::new()
.policy(Policy::Disabled)
.build_bytes(&mut data)
.unwrap();
assert_eq!(secret.expose(), &[1, 2, 3]);
}
#[test]
fn test_expose_guarded_lifecycle() {
let secret = ShroudedString::new("guarded_secret".to_string()).unwrap();
{
let guard = secret
.expose_guarded()
.expect("expose_guarded should succeed");
assert_eq!(&*guard, "guarded_secret");
}
let guard2 = secret
.expose_guarded()
.expect("second expose_guarded should succeed");
assert_eq!(&*guard2, "guarded_secret");
}
#[test]
fn test_expose_guarded_mut() {
let mut secret = ShroudBuilder::new()
.policy(Policy::Disabled)
.build_bytes(&mut [1, 2, 3, 4, 5])
.unwrap();
{
let mut guard = secret
.expose_guarded_mut()
.expect("expose_guarded_mut should succeed");
guard[0] = 10;
}
assert_eq!(secret.expose()[0], 10);
}
#[test]
fn test_expose_guarded_with_disabled_policy() {
let secret = ShroudBuilder::new()
.policy(Policy::Disabled)
.build_string("disabled_policy".to_string())
.unwrap();
let guard = secret
.expose_guarded()
.expect("should succeed even with disabled policy");
assert_eq!(&*guard, "disabled_policy");
}
#[test]
fn test_concurrent_read_access() {
use std::sync::Arc;
use std::thread;
let secret = Arc::new(ShroudedString::new("concurrent_secret".to_string()).unwrap());
let mut handles = vec![];
for _ in 0..4 {
let secret_clone = Arc::clone(&secret);
handles.push(thread::spawn(move || {
for _ in 0..100 {
let value = secret_clone.expose();
assert_eq!(value, "concurrent_secret");
}
}));
}
for handle in handles {
handle.join().expect("thread should not panic");
}
}
#[test]
fn test_concurrent_guarded_access() {
use std::sync::Arc;
use std::thread;
let secret = Arc::new(
ShroudBuilder::new()
.policy(Policy::Disabled)
.build_string("guarded_concurrent".to_string())
.unwrap(),
);
let mut handles = vec![];
for _ in 0..4 {
let secret_clone = Arc::clone(&secret);
handles.push(thread::spawn(move || {
for _ in 0..50 {
let guard = secret_clone
.expose_guarded()
.expect("expose_guarded should succeed");
assert_eq!(&*guard, "guarded_concurrent");
}
}));
}
for handle in handles {
handle.join().expect("thread should not panic");
}
}
#[test]
fn test_large_allocation_1mb() {
let size = 1024 * 1024; let mut data = vec![0xABu8; size];
let secret = ShroudedBytes::from_slice(&mut data).unwrap();
assert_eq!(secret.len(), size);
assert_eq!(secret.expose()[0], 0xAB);
assert_eq!(secret.expose()[size - 1], 0xAB);
assert!(data.iter().all(|&b| b == 0));
}
#[test]
fn test_near_overflow_size_fails_gracefully() {
let huge_size = usize::MAX / 2;
let result = ShroudBuilder::new()
.policy(Policy::BestEffort)
.build_bytes_with(huge_size, |_| {});
assert!(result.is_err());
}
#[test]
fn test_strict_policy_allocates_successfully() {
let result = ShroudBuilder::new()
.policy(Policy::Strict)
.build_string("strict_test".to_string());
match result {
Ok(secret) => assert_eq!(secret.expose(), "strict_test"),
Err(e) => {
let err_str = format!("{:?}", e);
assert!(
err_str.contains("Lock")
|| err_str.contains("Protect")
|| err_str.contains("Allocation"),
"unexpected error: {}",
err_str
);
}
}
}
#[test]
fn test_disabled_policy_always_succeeds() {
let secret = ShroudBuilder::new()
.policy(Policy::Disabled)
.build_string("always_works".to_string())
.expect("disabled policy should always succeed");
assert_eq!(secret.expose(), "always_works");
let guard = secret.expose_guarded().expect("guarded access should work");
assert_eq!(&*guard, "always_works");
}
#[test]
fn test_best_effort_policy_succeeds() {
let secret = ShroudBuilder::new()
.policy(Policy::BestEffort)
.build_string("best_effort_test".to_string())
.expect("best effort should succeed");
assert_eq!(secret.expose(), "best_effort_test");
}
}