use super::common;
use common::*;
use hexz_core::algo::compression::lz4::Lz4Compressor;
use hexz_core::algo::compression::zstd::ZstdCompressor;
use hexz_core::algo::encryption::aes_gcm::AesGcmEncryptor;
use hexz_core::format::header::Header;
use hexz_core::format::magic::HEADER_SIZE;
use hexz_core::{Archive, ArchiveStream};
use hexz_ops::pack::{PackConfig, PackTransformFlags, pack_archive};
use hexz_store::local::FileBackend;
use std::fs;
use std::io::Write;
use std::sync::Arc;
use tempfile::TempDir;
fn create_encrypted_archive(
temp_dir: &TempDir,
data: &[u8],
password: &str,
compression: &str,
) -> std::path::PathBuf {
let disk_path = temp_dir.path().join("disk.img");
fs::write(&disk_path, data).unwrap();
let output_path = temp_dir.path().join("encrypted.hxz");
let config = PackConfig {
input: disk_path,
output: output_path.clone(),
compression: compression.to_string(),
password: Some(password.to_string()),
transform: PackTransformFlags { encrypt: true, train_dict: false, ..Default::default() },
block_size: 65536,
..Default::default()
};
pack_archive(&config, None::<&fn(u64, u64)>).expect("Encrypted packing failed");
output_path
}
fn open_encrypted_archive(path: &std::path::Path, password: &str) -> Arc<Archive> {
let backend = Arc::new(FileBackend::new(path).unwrap());
let header_bytes = backend.read_exact(0, HEADER_SIZE).unwrap();
let header: Header = bincode::deserialize(&header_bytes).unwrap();
let compressor: Box<dyn hexz_core::algo::compression::Compressor> = match header.compression {
hexz_core::format::header::CompressionType::Lz4 => Box::new(Lz4Compressor::new()),
hexz_core::format::header::CompressionType::Zstd => Box::new(ZstdCompressor::new(3, None)),
};
let encryptor = header.encryption.as_ref().map(|params| {
Box::new(
AesGcmEncryptor::new(password.as_bytes(), ¶ms.salt, params.iterations).unwrap(),
) as Box<dyn hexz_core::algo::encryption::Encryptor>
});
Archive::new(backend, compressor, encryptor).unwrap()
}
use hexz_store::StorageBackend;
#[test]
fn test_encrypted_pack_read_lz4() {
let temp_dir = TempDir::new().unwrap();
let data = vec![0x42u8; 256 * 1024];
let password = "test_password_123";
let snap_path = create_encrypted_archive(&temp_dir, &data, password, "lz4");
let archive = open_encrypted_archive(&snap_path, password);
assert_eq!(archive.size(ArchiveStream::Main), 256 * 1024);
let read_data = archive.read_at(ArchiveStream::Main, 0, 4096).unwrap();
assert_eq!(read_data.len(), 4096);
assert!(read_data.iter().all(|&b| b == 0x42));
}
#[test]
fn test_encrypted_pack_read_zstd() {
let temp_dir = TempDir::new().unwrap();
let data = vec![0xAB; 128 * 1024];
let password = "zstd_secret";
let snap_path = create_encrypted_archive(&temp_dir, &data, password, "zstd");
let archive = open_encrypted_archive(&snap_path, password);
assert_eq!(archive.size(ArchiveStream::Main), 128 * 1024);
let read_data = archive.read_at(ArchiveStream::Main, 0, 1024).unwrap();
assert!(read_data.iter().all(|&b| b == 0xAB));
}
#[test]
fn test_encrypted_wrong_password_fails() {
let temp_dir = TempDir::new().unwrap();
let data = vec![0x42u8; 128 * 1024];
let correct_password = "correct_password";
let wrong_password = "wrong_password";
let snap_path = create_encrypted_archive(&temp_dir, &data, correct_password, "lz4");
let backend = Arc::new(FileBackend::new(&snap_path).unwrap());
let header_bytes = backend.read_exact(0, HEADER_SIZE).unwrap();
let header: Header = bincode::deserialize(&header_bytes).unwrap();
let encryptor = header.encryption.as_ref().map(|params| {
Box::new(
AesGcmEncryptor::new(wrong_password.as_bytes(), ¶ms.salt, params.iterations)
.unwrap(),
) as Box<dyn hexz_core::algo::encryption::Encryptor>
});
let compressor = Box::new(Lz4Compressor::new());
let archive = Archive::new(backend, compressor, encryptor).unwrap();
let result = archive.read_at(ArchiveStream::Main, 0, 4096);
assert!(result.is_err(), "Wrong password should cause read failure");
}
#[test]
fn test_encrypted_varied_data() {
let temp_dir = TempDir::new().unwrap();
let disk_path = temp_dir.path().join("disk.img");
let mut file = fs::File::create(&disk_path).unwrap();
for i in 0..8 {
let block = vec![(i * 37) as u8; 65536];
file.write_all(&block).unwrap();
}
drop(file);
let output_path = temp_dir.path().join("encrypted.hxz");
let password = "varied_data_pw";
let config = PackConfig {
input: disk_path,
output: output_path.clone(),
compression: "lz4".to_string(),
password: Some(password.to_string()),
transform: PackTransformFlags { encrypt: true, train_dict: false, ..Default::default() },
block_size: 65536,
..Default::default()
};
pack_archive(&config, None::<&fn(u64, u64)>).unwrap();
let archive = open_encrypted_archive(&output_path, password);
for i in 0..8u64 {
let expected = (i * 37) as u8;
let read = archive
.read_at(ArchiveStream::Main, i * 65536, 1024)
.unwrap();
assert!(
read.iter().all(|&b| b == expected),
"Block {i} mismatch: expected 0x{expected:02X}"
);
}
}
#[test]
fn test_encrypted_dual_stream() {
let temp_dir = TempDir::new().unwrap();
let input_dir = temp_dir.path().join("input");
fs::create_dir(&input_dir).unwrap();
fs::write(input_dir.join("disk"), vec![0xDD; 256 * 1024]).unwrap();
fs::write(input_dir.join("memory"), vec![0xCC; 128 * 1024]).unwrap();
let output_path = temp_dir.path().join("encrypted.hxz");
let password = "dual_stream_pw";
let config = PackConfig {
input: input_dir,
output: output_path.clone(),
compression: "lz4".to_string(),
password: Some(password.to_string()),
transform: PackTransformFlags { encrypt: true, train_dict: false, ..Default::default() },
block_size: 65536,
..Default::default()
};
pack_archive(&config, None::<&fn(u64, u64)>).unwrap();
let archive = open_encrypted_archive(&output_path, password);
assert_eq!(archive.size(ArchiveStream::Main), 256 * 1024);
assert_eq!(archive.size(ArchiveStream::Auxiliary), 128 * 1024);
let disk_read = archive.read_at(ArchiveStream::Main, 0, 1024).unwrap();
assert!(disk_read.iter().all(|&b| b == 0xDD));
let mem_read = archive
.read_at(ArchiveStream::Auxiliary, 0, 1024)
.unwrap();
assert!(mem_read.iter().all(|&b| b == 0xCC));
}
#[test]
fn test_encrypted_pack_no_password_fails() {
let temp_dir = TempDir::new().unwrap();
let disk_path = temp_dir.path().join("disk.img");
fs::write(&disk_path, vec![0u8; 65536]).unwrap();
let output_path = temp_dir.path().join("no_pw.hxz");
let config = PackConfig {
input: disk_path,
output: output_path,
compression: "lz4".to_string(),
password: None, transform: PackTransformFlags { encrypt: true, train_dict: false, ..Default::default() },
block_size: 65536,
..Default::default()
};
let result = pack_archive(&config, None::<&fn(u64, u64)>);
assert!(result.is_err(), "Should fail without password");
}
#[test]
fn test_encrypted_sequential_read() {
let temp_dir = TempDir::new().unwrap();
let data: Vec<u8> = (0..256 * 1024).map(|i| (i % 256) as u8).collect();
let password = "sequential_pw";
let snap_path = create_encrypted_archive(&temp_dir, &data, password, "lz4");
let archive = open_encrypted_archive(&snap_path, password);
let mut offset = 0u64;
let chunk_size = 32768;
while offset < data.len() as u64 {
let remaining = data.len() as u64 - offset;
let to_read = std::cmp::min(chunk_size, remaining as usize);
let chunk = archive
.read_at(ArchiveStream::Main, offset, to_read)
.unwrap();
assert_eq!(
&chunk[..],
&data[offset as usize..offset as usize + to_read],
"Mismatch at offset {offset}"
);
offset += to_read as u64;
}
}
#[test]
fn test_encrypted_random_data() {
let temp_dir = TempDir::new().unwrap();
let data = create_random_data(128 * 1024);
let password = "random_pw";
let snap_path = create_encrypted_archive(&temp_dir, &data, password, "lz4");
let archive = open_encrypted_archive(&snap_path, password);
let read_data = archive
.read_at(ArchiveStream::Main, 0, data.len())
.unwrap();
assert_bytes_equal(&read_data, &data, "encrypted random data round-trip");
}