use std::fmt;
use chrono::{DateTime, Utc};
use subtle::{Choice, ConstantTimeEq};
use zeroize::Zeroize;
pub struct ZeroizedBytes {
data: Vec<u8>,
}
impl ZeroizedBytes {
#[must_use]
pub fn new(data: Vec<u8>) -> Self {
Self { data }
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.data
}
#[must_use]
pub fn len(&self) -> usize {
self.data.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
impl Drop for ZeroizedBytes {
fn drop(&mut self) {
self.data.zeroize();
}
}
impl fmt::Debug for ZeroizedBytes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ZeroizedBytes").field("data", &"[REDACTED]").finish()
}
}
impl AsRef<[u8]> for ZeroizedBytes {
fn as_ref(&self) -> &[u8] {
&self.data
}
}
impl ConstantTimeEq for ZeroizedBytes {
fn ct_eq(&self, other: &Self) -> Choice {
self.data.ct_eq(&other.data)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PublicKey(Vec<u8>);
impl PublicKey {
#[must_use]
pub fn new(data: Vec<u8>) -> Self {
Self(data)
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.0
}
#[must_use]
pub fn into_bytes(self) -> Vec<u8> {
self.0
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl AsRef<[u8]> for PublicKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for PublicKey {
fn from(v: Vec<u8>) -> Self {
Self(v)
}
}
pub struct PrivateKey(ZeroizedBytes);
impl PrivateKey {
#[must_use]
pub fn new(data: Vec<u8>) -> Self {
Self(ZeroizedBytes::new(data))
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
self.0.as_slice()
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl fmt::Debug for PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("PrivateKey").field(&"[REDACTED]").finish()
}
}
impl AsRef<[u8]> for PrivateKey {
fn as_ref(&self) -> &[u8] {
self.0.as_slice()
}
}
impl ConstantTimeEq for PrivateKey {
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}
pub struct SymmetricKey(ZeroizedBytes);
impl SymmetricKey {
#[must_use]
pub fn new(data: Vec<u8>) -> Self {
Self(ZeroizedBytes::new(data))
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
self.0.as_slice()
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl fmt::Debug for SymmetricKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("SymmetricKey").field(&"[REDACTED]").finish()
}
}
impl AsRef<[u8]> for SymmetricKey {
fn as_ref(&self) -> &[u8] {
self.0.as_slice()
}
}
impl ConstantTimeEq for SymmetricKey {
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct HashOutput([u8; 32]);
impl HashOutput {
#[must_use]
pub const fn new(data: [u8; 32]) -> Self {
Self(data)
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.0
}
#[must_use]
pub const fn into_bytes(self) -> [u8; 32] {
self.0
}
}
impl AsRef<[u8]> for HashOutput {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<[u8; 32]> for HashOutput {
fn from(a: [u8; 32]) -> Self {
Self(a)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EncryptedMetadata {
pub nonce: Vec<u8>,
pub tag: Option<Vec<u8>>,
pub key_id: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SignedMetadata {
pub signature: Vec<u8>,
pub signature_algorithm: String,
pub public_key: Vec<u8>,
pub key_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CryptoPayload<T> {
pub data: Vec<u8>,
pub metadata: T,
pub scheme: String,
pub timestamp: u64,
}
pub type EncryptedData = CryptoPayload<EncryptedMetadata>;
pub type SignedData = CryptoPayload<SignedMetadata>;
pub struct KeyPair {
public_key: PublicKey,
private_key: PrivateKey,
}
impl Drop for KeyPair {
fn drop(&mut self) {
}
}
impl fmt::Debug for KeyPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("KeyPair")
.field("public_key", &self.public_key)
.field("private_key", &"[REDACTED]")
.finish()
}
}
impl ConstantTimeEq for KeyPair {
fn ct_eq(&self, other: &Self) -> Choice {
self.private_key.ct_eq(&other.private_key)
& self.public_key.as_slice().ct_eq(other.public_key.as_slice())
}
}
impl KeyPair {
#[must_use]
pub fn new(public_key: PublicKey, private_key: PrivateKey) -> Self {
Self { public_key, private_key }
}
#[must_use]
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
#[must_use]
pub fn private_key(&self) -> &PrivateKey {
&self.private_key
}
}
#[non_exhaustive]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
)]
#[serde(rename_all = "kebab-case")]
#[cfg_attr(kani, derive(kani::Arbitrary))]
pub enum CryptoMode {
#[default]
Hybrid,
PqOnly,
}
#[non_exhaustive]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
)]
#[serde(rename_all = "lowercase")]
#[cfg_attr(kani, derive(kani::Arbitrary))]
pub enum SecurityLevel {
Standard,
#[default]
High,
Maximum,
#[deprecated(
since = "0.6.0",
note = "Use SecurityLevel::Maximum with CryptoMode::PqOnly instead"
)]
Quantum,
}
impl SecurityLevel {
#[must_use]
#[allow(deprecated)]
pub fn resolve(self) -> (Self, CryptoMode) {
match self {
Self::Quantum => (Self::Maximum, CryptoMode::PqOnly),
other => (other, CryptoMode::Hybrid),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(kani, derive(kani::Arbitrary))]
pub enum ComplianceMode {
#[default]
Default,
Fips140_3,
Cnsa2_0,
}
impl ComplianceMode {
#[must_use]
pub const fn requires_fips(&self) -> bool {
matches!(self, Self::Fips140_3 | Self::Cnsa2_0)
}
#[must_use]
pub const fn allows_hybrid(&self) -> bool {
!matches!(self, Self::Cnsa2_0)
}
}
#[must_use]
pub const fn fips_available() -> bool {
cfg!(feature = "fips")
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(kani, derive(kani::Arbitrary))]
pub enum PerformancePreference {
Speed,
Memory,
#[default]
Balanced,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum UseCase {
SecureMessaging,
EmailEncryption,
VpnTunnel,
ApiSecurity,
FileStorage,
DatabaseEncryption,
CloudStorage,
BackupArchive,
ConfigSecrets,
Authentication,
SessionToken,
DigitalCertificate,
KeyExchange,
FinancialTransactions,
LegalDocuments,
BlockchainTransaction,
HealthcareRecords,
GovernmentClassified,
PaymentCard,
IoTDevice,
FirmwareSigning,
AuditLog,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(kani, derive(kani::Arbitrary))]
pub enum CryptoScheme {
Hybrid,
Symmetric,
SymmetricChaCha20,
Asymmetric,
PostQuantum,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SignatureScheme {
Ed25519,
MlDsa44,
MlDsa65,
MlDsa87,
SlhDsaShake128s,
SlhDsaShake192s,
SlhDsaShake256s,
FnDsa512,
FnDsa1024,
HybridMlDsa44Ed25519,
HybridMlDsa65Ed25519,
HybridMlDsa87Ed25519,
}
impl SignatureScheme {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Ed25519 => "ed25519",
Self::MlDsa44 => "ml-dsa-44",
Self::MlDsa65 => "ml-dsa-65",
Self::MlDsa87 => "ml-dsa-87",
Self::SlhDsaShake128s => "slh-dsa-shake-128s",
Self::SlhDsaShake192s => "slh-dsa-shake-192s",
Self::SlhDsaShake256s => "slh-dsa-shake-256s",
Self::FnDsa512 => "fn-dsa-512",
Self::FnDsa1024 => "fn-dsa-1024",
Self::HybridMlDsa44Ed25519 => "hybrid-ml-dsa-44-ed25519",
Self::HybridMlDsa65Ed25519 => "hybrid-ml-dsa-65-ed25519",
Self::HybridMlDsa87Ed25519 => "hybrid-ml-dsa-87-ed25519",
}
}
}
impl fmt::Display for SignatureScheme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl std::str::FromStr for SignatureScheme {
type Err = crate::types::error::TypeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ed25519" => Ok(Self::Ed25519),
"ml-dsa-44" => Ok(Self::MlDsa44),
"ml-dsa-65" => Ok(Self::MlDsa65),
"ml-dsa-87" => Ok(Self::MlDsa87),
"slh-dsa-shake-128s" => Ok(Self::SlhDsaShake128s),
"slh-dsa-shake-192s" => Ok(Self::SlhDsaShake192s),
"slh-dsa-shake-256s" => Ok(Self::SlhDsaShake256s),
"fn-dsa" | "fn-dsa-512" => Ok(Self::FnDsa512),
"fn-dsa-1024" => Ok(Self::FnDsa1024),
"hybrid-ml-dsa-44-ed25519" => Ok(Self::HybridMlDsa44Ed25519),
"hybrid-ml-dsa-65-ed25519" => Ok(Self::HybridMlDsa65Ed25519),
"hybrid-ml-dsa-87-ed25519" => Ok(Self::HybridMlDsa87Ed25519),
other => Err(crate::types::error::TypeError::UnknownScheme(other.to_string())),
}
}
}
#[derive(Debug, Clone)]
pub struct CryptoContext {
pub security_level: SecurityLevel,
pub performance_preference: PerformancePreference,
pub use_case: Option<UseCase>,
pub hardware_acceleration: bool,
pub timestamp: DateTime<Utc>,
}
impl Default for CryptoContext {
fn default() -> Self {
Self {
security_level: SecurityLevel::default(),
performance_preference: PerformancePreference::default(),
use_case: None,
hardware_acceleration: true,
timestamp: Utc::now(),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub enum AlgorithmSelection {
UseCase(UseCase),
SecurityLevel(SecurityLevel),
ForcedScheme(CryptoScheme),
}
impl Default for AlgorithmSelection {
fn default() -> Self {
Self::SecurityLevel(SecurityLevel::High)
}
}
#[cfg(kani)]
mod kani_proofs {
use super::*;
#[kani::proof]
fn security_level_default_is_high() {
let default = SecurityLevel::default();
kani::assert(
default == SecurityLevel::High,
"Default SecurityLevel must be High (NIST Level 3)",
);
}
#[kani::proof]
fn compliance_mode_default_is_unrestricted() {
let default = ComplianceMode::default();
kani::assert(
default == ComplianceMode::Default,
"Default ComplianceMode must be Default (unrestricted)",
);
kani::assert(!default.requires_fips(), "Default mode must not require FIPS");
kani::assert(default.allows_hybrid(), "Default mode must allow hybrid");
}
#[kani::proof]
fn cnsa_requires_fips() {
let cnsa = ComplianceMode::Cnsa2_0;
kani::assert(cnsa.requires_fips(), "CNSA 2.0 must require FIPS");
}
#[kani::proof]
fn cnsa_disallows_hybrid() {
let cnsa = ComplianceMode::Cnsa2_0;
kani::assert(!cnsa.allows_hybrid(), "CNSA 2.0 must disallow hybrid");
}
#[kani::proof]
fn compliance_mode_requires_fips_exhaustive() {
let mode: ComplianceMode = kani::any();
let requires = mode.requires_fips();
let expected = matches!(mode, ComplianceMode::Fips140_3 | ComplianceMode::Cnsa2_0);
kani::assert(requires == expected, "requires_fips() iff Fips140_3 or Cnsa2_0");
}
#[kani::proof]
fn compliance_mode_allows_hybrid_exhaustive() {
let mode: ComplianceMode = kani::any();
let allows = mode.allows_hybrid();
let expected = !matches!(mode, ComplianceMode::Cnsa2_0);
kani::assert(allows == expected, "allows_hybrid() iff not Cnsa2_0");
}
#[kani::proof]
fn performance_preference_default_is_balanced() {
let default = PerformancePreference::default();
kani::assert(
default == PerformancePreference::Balanced,
"Default PerformancePreference must be Balanced",
);
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, deprecated)]
mod tests {
use super::*;
#[test]
fn test_zeroized_bytes_new_stores_data_succeeds() {
let data = vec![1u8, 2, 3, 4, 5];
let zb = ZeroizedBytes::new(data.clone());
assert_eq!(zb.as_slice(), &data);
}
#[test]
fn test_zeroized_bytes_len_returns_correct_length_has_correct_size() {
let zb = ZeroizedBytes::new(vec![0u8; 32]);
assert_eq!(zb.len(), 32);
assert!(!zb.is_empty());
}
#[test]
fn test_zeroized_bytes_empty_has_zero_length_has_correct_size() {
let zb = ZeroizedBytes::new(vec![]);
assert_eq!(zb.len(), 0);
assert!(zb.is_empty());
}
#[test]
fn test_zeroized_bytes_as_ref_returns_slice_succeeds() {
let data = vec![10u8, 20, 30];
let zb = ZeroizedBytes::new(data.clone());
let slice: &[u8] = zb.as_ref();
assert_eq!(slice, &data);
}
#[test]
fn test_zeroized_bytes_debug_redacts_content_succeeds() {
let zb = ZeroizedBytes::new(vec![1, 2, 3]);
let debug = format!("{:?}", zb);
assert!(debug.contains("ZeroizedBytes"));
}
#[test]
fn test_encrypted_metadata_with_tag_sets_fields_succeeds() {
let meta = EncryptedMetadata {
nonce: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
tag: Some(vec![0xAA; 16]),
key_id: Some("key-001".to_string()),
};
assert_eq!(meta.nonce.len(), 12);
assert!(meta.tag.is_some());
assert_eq!(meta.key_id.as_deref(), Some("key-001"));
}
#[test]
fn test_encrypted_metadata_without_tag_sets_none_tag_succeeds() {
let meta = EncryptedMetadata { nonce: vec![0u8; 12], tag: None, key_id: None };
assert!(meta.tag.is_none());
assert!(meta.key_id.is_none());
}
#[test]
fn test_encrypted_metadata_eq_compares_all_fields_succeeds() {
let meta1 = EncryptedMetadata { nonce: vec![1, 2, 3], tag: None, key_id: None };
let meta2 = meta1.clone();
assert_eq!(meta1, meta2);
}
#[test]
fn test_signed_metadata_clone_debug_work_correctly_succeeds() {
let meta = SignedMetadata {
signature: vec![0xBB; 64],
signature_algorithm: "ML-DSA-65".to_string(),
public_key: vec![0xCC; 32],
key_id: Some("sig-key-001".to_string()),
};
let cloned = meta.clone();
assert_eq!(cloned.signature_algorithm, "ML-DSA-65");
assert_eq!(cloned.public_key.len(), 32);
let debug = format!("{:?}", meta);
assert!(debug.contains("SignedMetadata"));
}
#[test]
fn test_crypto_payload_clone_eq_work_correctly_succeeds() {
let payload: CryptoPayload<EncryptedMetadata> = CryptoPayload {
data: vec![1, 2, 3],
metadata: EncryptedMetadata { nonce: vec![0u8; 12], tag: None, key_id: None },
scheme: "AES-256-GCM".to_string(),
timestamp: 1234567890,
};
let cloned = payload.clone();
assert_eq!(payload, cloned);
assert_eq!(payload.scheme, "AES-256-GCM");
assert_eq!(payload.timestamp, 1234567890);
}
#[test]
fn test_keypair_new_stores_keys_succeeds() {
let pk = PublicKey::new(vec![1u8; 32]);
let sk = PrivateKey::new(vec![2u8; 64]);
let kp = KeyPair::new(pk.clone(), sk);
assert_eq!(kp.public_key(), &pk);
assert_eq!(kp.private_key().len(), 64);
}
#[test]
fn test_keypair_debug_redacts_secret_succeeds() {
let kp = KeyPair::new(PublicKey::new(vec![1u8; 32]), PrivateKey::new(vec![2u8; 32]));
let debug = format!("{:?}", kp);
assert!(debug.contains("KeyPair"));
}
#[test]
fn test_security_level_default_is_standard_succeeds() {
assert_eq!(SecurityLevel::default(), SecurityLevel::High);
}
#[test]
fn test_security_level_variants_produce_correct_bit_security_succeeds() {
let variants = vec![
SecurityLevel::Standard,
SecurityLevel::High,
SecurityLevel::Maximum,
SecurityLevel::Quantum,
];
for v in &variants {
assert_eq!(*v, v.clone());
}
assert_ne!(SecurityLevel::Standard, SecurityLevel::Maximum);
}
#[test]
fn test_security_level_debug_produces_nonempty_string_succeeds() {
let debug = format!("{:?}", SecurityLevel::Quantum);
assert!(debug.contains("Quantum"));
}
#[test]
fn test_security_level_resolve_quantum_returns_maximum_pq_only() {
let (level, mode) = SecurityLevel::Quantum.resolve();
assert_eq!(level, SecurityLevel::Maximum);
assert_eq!(mode, CryptoMode::PqOnly);
}
#[test]
fn test_security_level_resolve_standard_returns_hybrid() {
let (level, mode) = SecurityLevel::Standard.resolve();
assert_eq!(level, SecurityLevel::Standard);
assert_eq!(mode, CryptoMode::Hybrid);
}
#[test]
fn test_security_level_resolve_high_returns_hybrid() {
let (level, mode) = SecurityLevel::High.resolve();
assert_eq!(level, SecurityLevel::High);
assert_eq!(mode, CryptoMode::Hybrid);
}
#[test]
fn test_security_level_resolve_maximum_returns_hybrid() {
let (level, mode) = SecurityLevel::Maximum.resolve();
assert_eq!(level, SecurityLevel::Maximum);
assert_eq!(mode, CryptoMode::Hybrid);
}
#[test]
fn test_crypto_mode_default_is_hybrid() {
assert_eq!(CryptoMode::default(), CryptoMode::Hybrid);
}
#[test]
fn test_crypto_mode_serde_roundtrip() {
let pq = CryptoMode::PqOnly;
let json = serde_json::to_string(&pq).unwrap();
assert_eq!(json, "\"pq-only\"");
let parsed: CryptoMode = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, CryptoMode::PqOnly);
let hybrid = CryptoMode::Hybrid;
let json = serde_json::to_string(&hybrid).unwrap();
assert_eq!(json, "\"hybrid\"");
let parsed: CryptoMode = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, CryptoMode::Hybrid);
}
#[test]
fn test_crypto_mode_serde_rejects_unknown_string() {
let result = serde_json::from_str::<CryptoMode>("\"classic\"");
assert!(result.is_err());
}
#[test]
fn test_crypto_mode_variants_are_distinct() {
assert_ne!(CryptoMode::Hybrid, CryptoMode::PqOnly);
}
#[test]
fn test_performance_preference_default_is_balanced_succeeds() {
assert_eq!(PerformancePreference::default(), PerformancePreference::Balanced);
}
#[test]
fn test_performance_preference_variants_are_distinct_are_unique() {
assert_ne!(PerformancePreference::Speed, PerformancePreference::Memory);
assert_eq!(PerformancePreference::Speed, PerformancePreference::Speed.clone());
}
#[test]
fn test_use_case_variants_produce_correct_descriptions_is_documented() {
let cases = vec![
UseCase::SecureMessaging,
UseCase::EmailEncryption,
UseCase::VpnTunnel,
UseCase::ApiSecurity,
UseCase::FileStorage,
UseCase::DatabaseEncryption,
UseCase::CloudStorage,
UseCase::BackupArchive,
UseCase::ConfigSecrets,
UseCase::Authentication,
UseCase::SessionToken,
UseCase::DigitalCertificate,
UseCase::KeyExchange,
UseCase::FinancialTransactions,
UseCase::LegalDocuments,
UseCase::BlockchainTransaction,
UseCase::HealthcareRecords,
UseCase::GovernmentClassified,
UseCase::PaymentCard,
UseCase::IoTDevice,
UseCase::FirmwareSigning,
UseCase::AuditLog,
];
for c in &cases {
assert_eq!(*c, c.clone());
}
assert_ne!(UseCase::SecureMessaging, UseCase::FileStorage);
}
#[test]
fn test_crypto_scheme_variants_are_distinct_are_unique() {
let schemes = vec![
CryptoScheme::Hybrid,
CryptoScheme::Symmetric,
CryptoScheme::Asymmetric,
CryptoScheme::PostQuantum,
];
for s in &schemes {
assert_eq!(*s, s.clone());
}
assert_ne!(CryptoScheme::Hybrid, CryptoScheme::Symmetric);
}
#[test]
fn test_crypto_context_default_sets_expected_fields_succeeds() {
let ctx = CryptoContext::default();
assert_eq!(ctx.security_level, SecurityLevel::High);
assert_eq!(ctx.performance_preference, PerformancePreference::Balanced);
assert!(ctx.use_case.is_none());
assert!(ctx.hardware_acceleration);
}
#[test]
fn test_crypto_context_clone_debug_work_correctly_succeeds() {
let ctx = CryptoContext::default();
let cloned = ctx.clone();
assert_eq!(cloned.security_level, ctx.security_level);
let debug = format!("{:?}", ctx);
assert!(debug.contains("CryptoContext"));
}
#[test]
fn test_algorithm_selection_default_is_automatic_succeeds() {
let sel = AlgorithmSelection::default();
assert_eq!(sel, AlgorithmSelection::SecurityLevel(SecurityLevel::High));
}
#[test]
fn test_algorithm_selection_use_case_sets_use_case_field_succeeds() {
let sel = AlgorithmSelection::UseCase(UseCase::FileStorage);
assert_eq!(sel, AlgorithmSelection::UseCase(UseCase::FileStorage));
assert_ne!(sel, AlgorithmSelection::default());
}
#[test]
fn test_algorithm_selection_forced_scheme_sets_forced_field_succeeds() {
let sel = AlgorithmSelection::ForcedScheme(CryptoScheme::PostQuantum);
assert_eq!(sel, AlgorithmSelection::ForcedScheme(CryptoScheme::PostQuantum));
assert_ne!(sel, AlgorithmSelection::default());
assert_ne!(sel, AlgorithmSelection::UseCase(UseCase::FileStorage));
}
#[test]
fn test_compliance_mode_default_is_standard_succeeds() {
let mode = ComplianceMode::default();
assert_eq!(mode, ComplianceMode::Default);
}
#[test]
fn test_compliance_mode_requires_fips_returns_correct_bool_succeeds() {
assert!(!ComplianceMode::Default.requires_fips());
assert!(ComplianceMode::Fips140_3.requires_fips());
assert!(ComplianceMode::Cnsa2_0.requires_fips());
}
#[test]
fn test_compliance_mode_allows_hybrid_returns_correct_bool_succeeds() {
assert!(ComplianceMode::Default.allows_hybrid());
assert!(ComplianceMode::Fips140_3.allows_hybrid());
assert!(!ComplianceMode::Cnsa2_0.allows_hybrid());
}
#[test]
fn test_compliance_mode_clone_eq_work_correctly_succeeds() {
let mode = ComplianceMode::Fips140_3;
assert_eq!(mode, mode.clone());
assert_ne!(ComplianceMode::Default, ComplianceMode::Fips140_3);
assert_ne!(ComplianceMode::Fips140_3, ComplianceMode::Cnsa2_0);
}
#[test]
fn test_compliance_mode_debug_produces_nonempty_string_succeeds() {
let debug = format!("{:?}", ComplianceMode::Fips140_3);
assert!(debug.contains("Fips140_3"));
}
#[test]
fn test_fips_available_returns_bool_succeeds() {
let available = fips_available();
#[cfg(feature = "fips")]
assert!(available);
#[cfg(not(feature = "fips"))]
assert!(!available);
}
}