mod common;
use cachekit_core::byte_storage::{ByteStorage, ByteStorageError};
use common::fixtures::*;
#[cfg(feature = "compression")]
mod compression_tests {
use super::*;
#[test]
fn test_empty_data_compresses_correctly() {
let storage = ByteStorage::new(None);
let envelope_bytes = storage
.store(EMPTY_DATA, None)
.expect("Empty data should compress");
let (retrieved, format) = storage
.retrieve(&envelope_bytes)
.expect("Should retrieve empty data");
assert_eq!(retrieved, EMPTY_DATA);
assert_eq!(format, "msgpack");
}
#[test]
fn test_small_data_compresses() {
let storage = ByteStorage::new(None);
let envelope_bytes = storage
.store(SMALL_DATA, None)
.expect("Small data should compress");
let (retrieved, format) = storage.retrieve(&envelope_bytes).expect("Should retrieve");
assert_eq!(retrieved, SMALL_DATA);
assert_eq!(format, "msgpack");
assert!(
envelope_bytes.len() < 1000,
"Small data envelope should be compact"
);
}
#[test]
fn test_large_data_compresses() {
let storage = ByteStorage::new(None);
let mut large_data = Vec::new();
let template = br#"{"id":XXXXX,"name":"userXXXXX","email":"user@example.com","timestamp":1234567890,"data":"#;
for i in 0..2500 {
large_data.extend_from_slice(template);
let incompressible_chunk = generate_incompressible_data(150, i as u64);
large_data.extend_from_slice(&incompressible_chunk);
large_data.extend_from_slice(br#""},"#);
}
let envelope_bytes = storage
.store(&large_data, None)
.expect("Large data should compress");
assert!(
envelope_bytes.len() < large_data.len() * 2,
"Envelope should not expand excessively (got ratio: {:.1}x)",
large_data.len() as f64 / envelope_bytes.len() as f64
);
let (retrieved, _) = storage
.retrieve(&envelope_bytes)
.expect("Should retrieve large data");
assert_eq!(retrieved, large_data);
}
#[test]
fn test_incompressible_data_handled() {
let storage = ByteStorage::new(None);
let incompressible = generate_incompressible_data(100_000, 42);
let envelope_bytes = storage
.store(&incompressible, None)
.expect("Incompressible data should still be stored");
let size_ratio = envelope_bytes.len() as f64 / incompressible.len() as f64;
assert!(
size_ratio < 1.6,
"Incompressible data should not expand excessively (got ratio: {:.2})",
size_ratio
);
let (retrieved, _) = storage.retrieve(&envelope_bytes).expect("Should retrieve");
assert_eq!(retrieved, incompressible);
}
#[test]
fn test_estimate_compression_accuracy() {
let storage = ByteStorage::new(None);
let compressible = generate_large_data(10_000, 0x42);
let ratio = storage
.estimate_compression(&compressible)
.expect("Should estimate compression");
assert!(
ratio > 10.0,
"Compressible data should have high ratio (got {:.1})",
ratio
);
let incompressible = generate_incompressible_data(10_000, 123);
let ratio_incomp = storage
.estimate_compression(&incompressible)
.expect("Should estimate");
assert!(
ratio_incomp < 1.5,
"Incompressible data should have low ratio (got {:.1})",
ratio_incomp
);
}
}
#[cfg(feature = "compression")]
mod security_limits {
use super::*;
const MAX_UNCOMPRESSED_SIZE: usize = 512 * 1024 * 1024; const MAX_COMPRESSED_SIZE: usize = 512 * 1024 * 1024;
#[test]
fn test_max_uncompressed_size_rejected() {
let storage = ByteStorage::new(None);
let oversized = 513 * 1024 * 1024;
let _sample_data = vec![0u8; oversized.min(1024)];
let _max_allowed_size = MAX_UNCOMPRESSED_SIZE - 1;
let small_data = vec![0u8; 1000];
assert!(
storage.store(&small_data, None).is_ok(),
"Small data should be accepted"
);
}
#[test]
fn test_max_compressed_size_logic() {
let storage = ByteStorage::new(None);
let data = generate_large_data(100_000, 0x00); let envelope = storage.store(&data, None).expect("Should compress");
assert!(
envelope.len() < MAX_COMPRESSED_SIZE,
"Compressed envelope should be under 512MB (got {} bytes)",
envelope.len()
);
assert!(
envelope.len() < 10_000,
"Highly compressible data should produce small envelope (got {} bytes)",
envelope.len()
);
}
#[test]
fn test_decompression_bomb_protection() {
let storage = ByteStorage::new(None);
let safe_data = generate_incompressible_data(10_000, 42); let safe_envelope = storage.store(&safe_data, None).expect("Should compress");
let (retrieved, _) = storage
.retrieve(&safe_envelope)
.expect("Safe ratio should decompress");
assert_eq!(retrieved, safe_data);
use rmp_serde;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct MaliciousEnvelope {
compressed_data: Vec<u8>,
checksum: [u8; 8], original_size: u32,
format: String,
}
let malicious = MaliciousEnvelope {
compressed_data: vec![0u8; 100], checksum: [0u8; 8], original_size: 500_000_000, format: "msgpack".to_string(),
};
let malicious_bytes = rmp_serde::to_vec(&malicious).expect("Serialize malicious envelope");
let result = storage.retrieve(&malicious_bytes);
assert!(
result.is_err(),
"Decompression bomb should be rejected during retrieve"
);
let error = result.unwrap_err();
assert!(
matches!(error, ByteStorageError::DecompressionBomb),
"Error should be DecompressionBomb variant, got: {:?}",
error
);
}
#[test]
fn test_compression_ratio_calculation() {
let storage = ByteStorage::new(None);
let compressible = generate_large_data(10_000, 0xFF);
let envelope_comp = storage.store(&compressible, None).expect("Should compress");
let ratio_comp = compressible.len() as f64 / envelope_comp.len() as f64;
assert!(
ratio_comp > 5.0,
"Compressible data should achieve >5x ratio (got {:.1}x)",
ratio_comp
);
let incompressible = generate_incompressible_data(10_000, 99);
let envelope_incomp = storage.store(&incompressible, None).expect("Should store");
let ratio_incomp = incompressible.len() as f64 / envelope_incomp.len() as f64;
assert!(
ratio_incomp < 2.0,
"Incompressible data should have low ratio (got {:.1}x)",
ratio_incomp
);
let estimate = storage
.estimate_compression(&compressible)
.expect("Should estimate");
assert!(
estimate > 1.0,
"Compression estimate should be positive (got {:.1}x)",
estimate
);
let estimate_incomp = storage
.estimate_compression(&incompressible)
.expect("Should estimate");
assert!(
estimate > estimate_incomp,
"Compressible estimate {:.1}x should exceed incompressible {:.1}x",
estimate,
estimate_incomp
);
}
}
#[cfg(all(feature = "compression", feature = "checksum"))]
mod checksum_validation {
use super::*;
#[test]
fn test_corrupted_checksum_rejected() {
let storage = ByteStorage::new(None);
let data = SMALL_DATA;
let mut envelope = storage.store(data, None).expect("Should create envelope");
if envelope.len() > 10 {
envelope[5] ^= 0xFF; }
let result = storage.retrieve(&envelope);
assert!(result.is_err(), "Corrupted envelope should be rejected");
let error_msg = format!("{:?}", result.unwrap_err());
println!("Corruption error: {}", error_msg);
}
#[test]
fn test_checksum_validates_after_decompression() {
let storage = ByteStorage::new(None);
let data = generate_incompressible_data(10_000, 777);
let envelope = storage.store(&data, None).expect("Should store");
let (retrieved, _) = storage
.retrieve(&envelope)
.expect("Valid checksum should pass");
assert_eq!(
retrieved, data,
"Data should match after checksum validation"
);
}
#[test]
fn test_checksum_integrity_protection() {
let storage = ByteStorage::new(None);
let data1 = b"original data";
let data2 = b"modified data";
let envelope1 = storage.store(data1, None).expect("Should store");
let envelope2 = storage.store(data2, None).expect("Should store");
assert_ne!(
envelope1, envelope2,
"Different data should have different checksums"
);
let (retrieved1, _) = storage.retrieve(&envelope1).expect("Should retrieve");
let (retrieved2, _) = storage.retrieve(&envelope2).expect("Should retrieve");
assert_eq!(retrieved1, data1);
assert_eq!(retrieved2, data2);
}
#[test]
fn test_multi_byte_corruption_detection() {
let storage = ByteStorage::new(None);
let data = generate_incompressible_data(5_000, 999);
let mut envelope = storage.store(&data, None).expect("Should store");
if envelope.len() > 102 {
envelope[100] ^= 0xFF; envelope[101] ^= 0xFF; }
let result = storage.retrieve(&envelope);
assert!(result.is_err(), "Two-byte corruption should be detected");
let mut envelope = storage.store(&data, None).expect("Should store");
if envelope.len() > 50 {
envelope[10] ^= 0xAA;
envelope[25] ^= 0x55;
envelope[40] ^= 0xCC;
}
let result = storage.retrieve(&envelope);
assert!(
result.is_err(),
"Multi-byte non-adjacent corruption should be detected"
);
println!("✓ Multi-byte corruption: Adjacent and non-adjacent flips detected");
}
#[test]
fn test_corruption_at_various_offsets() {
let storage = ByteStorage::new(None);
let data = generate_incompressible_data(10_000, 777);
let original_envelope = storage.store(&data, None).expect("Should store");
for offset in 0..10.min(original_envelope.len()) {
let mut envelope = original_envelope.clone();
envelope[offset] ^= 0xFF;
let result = storage.retrieve(&envelope);
assert!(
result.is_err(),
"Corruption at offset {} (start) should be detected",
offset
);
}
let mid = original_envelope.len() / 2;
for offset in mid.saturating_sub(5)..mid.saturating_add(5).min(original_envelope.len()) {
let mut envelope = original_envelope.clone();
envelope[offset] ^= 0xFF;
let result = storage.retrieve(&envelope);
assert!(
result.is_err(),
"Corruption at offset {} (middle) should be detected",
offset
);
}
let len = original_envelope.len();
for offset in len.saturating_sub(10)..len {
let mut envelope = original_envelope.clone();
envelope[offset] ^= 0xFF;
let result = storage.retrieve(&envelope);
assert!(
result.is_err(),
"Corruption at offset {} (end) should be detected",
offset
);
}
println!("✓ Corruption detection: Start, middle, and end verified");
}
#[test]
fn test_subtle_multi_byte_patterns() {
let storage = ByteStorage::new(None);
let data = generate_incompressible_data(5_000, 12345);
let original_envelope = storage.store(&data, None).expect("Should store");
let mut envelope1 = original_envelope.clone();
if envelope1.len() > 32 {
envelope1[20] ^= 0xAA;
envelope1[21] ^= 0x55; }
assert!(
storage.retrieve(&envelope1).is_err(),
"Complementary XOR pattern should be detected"
);
let mut envelope2 = original_envelope.clone();
if envelope2.len() > 50 {
let (pos1, pos2) = (20..envelope2.len() - 10)
.find_map(|i| {
if envelope2[i] != envelope2[i + 10] {
Some((i, i + 10))
} else {
None
}
})
.expect("Should find two different bytes in envelope");
envelope2.swap(pos1, pos2);
assert!(
storage.retrieve(&envelope2).is_err(),
"Byte swap should be detected"
);
}
let mut envelope3 = original_envelope.clone();
if envelope3.len() > 60 {
envelope3[50] = envelope3[50].wrapping_add(1);
envelope3[51] = envelope3[51].wrapping_add(1);
envelope3[52] = envelope3[52].wrapping_add(1);
}
assert!(
storage.retrieve(&envelope3).is_err(),
"Multi-byte increment should be detected"
);
let mut envelope4 = original_envelope.clone();
if envelope4.len() > 64 {
envelope4[60..64].iter_mut().for_each(|byte| *byte ^= 0x01); }
assert!(
storage.retrieve(&envelope4).is_err(),
"Aligned block corruption should be detected"
);
println!("✓ Subtle patterns: XOR, swap, increment, and block corruption detected");
}
}
#[cfg(feature = "compression")]
mod truncated_data {
use super::*;
#[test]
fn test_truncated_envelope_various_lengths() {
let storage = ByteStorage::new(None);
let data = generate_incompressible_data(1_000, 555);
let envelope = storage.store(&data, None).expect("Should store");
let test_lengths = vec![
0,
1,
5,
10,
20,
envelope.len() / 2,
envelope.len() - 10,
envelope.len() - 1,
];
for truncate_len in test_lengths {
if truncate_len >= envelope.len() {
continue; }
let truncated = &envelope[..truncate_len];
let result = storage.retrieve(truncated);
assert!(
result.is_err(),
"Truncated envelope (len={}) should be rejected (original len={})",
truncate_len,
envelope.len()
);
}
println!("✓ Truncated envelopes: All truncation points properly rejected");
}
#[test]
fn test_truncated_compressed_data() {
let storage = ByteStorage::new(None);
let data = generate_large_data(5_000, 0xAA); let mut envelope = storage.store(&data, None).expect("Should store");
if envelope.len() > 20 {
envelope.truncate(envelope.len() - 15);
}
let result = storage.retrieve(&envelope);
assert!(result.is_err(), "Truncated compressed data should fail");
println!("✓ Truncated compressed data: Properly rejected");
}
#[test]
fn test_incomplete_envelope_structure() {
let storage = ByteStorage::new(None);
let data = b"test data for incomplete envelope";
let envelope = storage.store(data, None).expect("Should store");
for len in 1..20.min(envelope.len()) {
let incomplete = &envelope[..len];
let result = storage.retrieve(incomplete);
assert!(
result.is_err(),
"Incomplete envelope structure (len={}) should be rejected",
len
);
}
println!("✓ Incomplete structures: All partial envelopes rejected");
}
#[test]
fn test_zero_length_envelope() {
let storage = ByteStorage::new(None);
let empty_envelope: &[u8] = b"";
let result = storage.retrieve(empty_envelope);
assert!(result.is_err(), "Zero-length envelope should be rejected");
let error = result.unwrap_err();
assert!(
matches!(error, ByteStorageError::DeserializationFailed(_)),
"Expected DeserializationFailed for empty envelope"
);
println!("✓ Zero-length envelope: Properly rejected");
}
#[test]
fn test_single_byte_envelope() {
let storage = ByteStorage::new(None);
let single_byte: &[u8] = b"X";
let result = storage.retrieve(single_byte);
assert!(result.is_err(), "Single-byte envelope should be rejected");
println!("✓ Single-byte envelope: Properly rejected");
}
}
#[cfg(feature = "compression")]
mod error_messages {
use super::*;
#[test]
fn test_invalid_envelope_clear_error() {
let storage = ByteStorage::new(None);
let invalid_data = b"this is not a valid envelope";
let result = storage.retrieve(invalid_data);
assert!(result.is_err(), "Invalid envelope should fail");
let error_msg = format!("{:?}", result.unwrap_err());
assert!(!error_msg.is_empty(), "Error message should be non-empty");
println!("Invalid envelope error: {}", error_msg);
}
#[test]
fn test_empty_envelope_clear_error() {
let storage = ByteStorage::new(None);
let empty_envelope: &[u8] = b"";
let result = storage.retrieve(empty_envelope);
assert!(result.is_err(), "Empty envelope should fail");
let error_msg = format!("{:?}", result.unwrap_err());
assert!(!error_msg.is_empty(), "Error message should be non-empty");
println!("Empty envelope error: {}", error_msg);
}
#[test]
fn test_truncated_envelope_clear_error() {
let storage = ByteStorage::new(None);
let data = SMALL_DATA;
let mut envelope = storage.store(data, None).expect("Should create");
if envelope.len() > 5 {
envelope.truncate(5);
}
let result = storage.retrieve(&envelope);
assert!(result.is_err(), "Truncated envelope should fail");
let error_msg = format!("{:?}", result.unwrap_err());
assert!(!error_msg.is_empty(), "Error message should be non-empty");
println!("Truncated envelope error: {}", error_msg);
}
}
#[cfg(feature = "compression")]
mod roundtrip {
use super::*;
#[test]
fn test_roundtrip_empty_data() {
let storage = ByteStorage::new(None);
let envelope = storage.store(EMPTY_DATA, None).expect("Should store empty");
let (retrieved, format) = storage.retrieve(&envelope).expect("Should retrieve empty");
assert_eq!(retrieved, EMPTY_DATA);
assert_eq!(format, "msgpack");
}
#[test]
fn test_roundtrip_small_data() {
let storage = ByteStorage::new(None);
let envelope = storage.store(SMALL_DATA, None).expect("Should store");
let (retrieved, format) = storage.retrieve(&envelope).expect("Should retrieve");
assert_eq!(retrieved, SMALL_DATA);
assert_eq!(format, "msgpack");
}
#[test]
fn test_roundtrip_large_data() {
let storage = ByteStorage::new(None);
let large_data = generate_incompressible_data(10_000_000, 12345);
let envelope = storage
.store(&large_data, None)
.expect("Should store large data");
let (retrieved, format) = storage
.retrieve(&envelope)
.expect("Should retrieve large data");
assert_eq!(retrieved.len(), large_data.len(), "Size should match");
assert_eq!(retrieved, large_data, "Data should match exactly");
assert_eq!(format, "msgpack");
println!("Large data roundtrip: {}MB", large_data.len() / 1_000_000);
}
#[test]
fn test_roundtrip_unicode_data() {
let storage = ByteStorage::new(None);
let envelope = storage
.store(UNICODE_DATA, None)
.expect("Should store unicode");
let (retrieved, format) = storage
.retrieve(&envelope)
.expect("Should retrieve unicode");
assert_eq!(retrieved, UNICODE_DATA);
assert_eq!(format, "msgpack");
let as_str = std::str::from_utf8(&retrieved).expect("Should be valid UTF-8");
println!("Unicode roundtrip: {}", as_str);
}
}