use std::alloc::System;
use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;
use ferrocrypt::secrecy::SecretString;
use ferrocrypt::{Encryptor, PublicKey};
use ferrocrypt_test_support::{
fast_keypair_generator, per_process_workspace, remove_per_process_workspace,
};
use stats_alloc::{INSTRUMENTED_SYSTEM, Region, StatsAlloc};
#[global_allocator]
static GLOBAL: &StatsAlloc<System> = &INSTRUMENTED_SYSTEM;
const PASSPHRASE: &str = "memory-bounds-test-passphrase";
const TEST_WORKSPACE: &str = "tests/workspace_memory_bounds";
const MIB: usize = 1024 * 1024;
const FILE_BYTES: usize = 64 * MIB;
const ALLOC_BOUND: usize = 8 * MIB;
#[ctor::dtor]
fn cleanup() {
remove_per_process_workspace(TEST_WORKSPACE);
}
fn fresh_workspace(name: &str) -> PathBuf {
let dir = per_process_workspace(TEST_WORKSPACE).join(name);
if dir.exists() {
fs::remove_dir_all(&dir).expect("clean memory-bounds workspace");
}
fs::create_dir_all(&dir).expect("create memory-bounds workspace");
dir
}
#[test]
fn public_key_encrypt_streams_without_buffering_whole_file() {
let work = fresh_workspace("stream_bound");
let keys = work.join("keys");
fs::create_dir_all(&keys).unwrap();
let kg = fast_keypair_generator(SecretString::from(PASSPHRASE.to_string()))
.write(&keys, |_| {})
.expect("keygen");
let key = PublicKey::from_key_file(&kg.public_key_path);
let input = work.join("large.bin");
{
let mut f = File::create(&input).unwrap();
let chunk = vec![0xA5u8; MIB];
for _ in 0..(FILE_BYTES / MIB) {
f.write_all(&chunk).unwrap();
}
f.sync_all().unwrap();
}
let out_dir = work.join("out");
fs::create_dir_all(&out_dir).unwrap();
let region = Region::new(GLOBAL);
Encryptor::with_public_key(key)
.write(&input, &out_dir, |_| {})
.expect("encrypt");
let allocated = region.change().bytes_allocated;
eprintln!(
"bytes allocated during {} MiB encrypt: {} bytes ({} KiB)",
FILE_BYTES / MIB,
allocated,
allocated / 1024,
);
assert!(
allocated < ALLOC_BOUND,
"a {} MiB encrypt allocated {allocated} bytes, over the {} MiB streaming bound — \
the payload appears to be buffered rather than streamed",
FILE_BYTES / MIB,
ALLOC_BOUND / MIB,
);
}