use chisel::{Chisel, Key, Options};
use std::io::{Seek, SeekFrom, Write};
use zeroize::Zeroizing;
fn raw_key(b: u8) -> Key {
Key::Raw(Zeroizing::new(vec![b; 32]))
}
#[test]
fn round_trip_open_with_correct_key() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("e.chisel");
let handle;
{
let mut db = Chisel::open(&path, Options::default().encryption_key(raw_key(0x11))).unwrap();
db.begin().unwrap();
handle = db.allocate(b"hello world").unwrap();
db.commit().unwrap();
}
{
let db = Chisel::open(
&path,
Options::default()
.encryption_key(raw_key(0x11))
.create_if_missing(false),
)
.unwrap();
assert_eq!(db.read(handle).unwrap(), b"hello world");
}
}
#[test]
fn wrong_key_is_operational_error_not_panic() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("e.chisel");
{
let mut db = Chisel::open(&path, Options::default().encryption_key(raw_key(0x11))).unwrap();
db.begin().unwrap();
db.commit().unwrap();
}
let err = Chisel::open(
&path,
Options::default()
.encryption_key(raw_key(0x22))
.create_if_missing(false),
);
assert!(err.is_err(), "wrong key must fail to open");
let ok = Chisel::open(
&path,
Options::default()
.encryption_key(raw_key(0x11))
.create_if_missing(false),
);
assert!(
ok.is_ok(),
"correct key must succeed after a wrong-key attempt"
);
}
#[test]
fn missing_key_on_encrypted_db_errors() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("e.chisel");
{
let mut db = Chisel::open(&path, Options::default().encryption_key(raw_key(0x11))).unwrap();
db.begin().unwrap();
db.commit().unwrap();
}
let err = Chisel::open(&path, Options::default().create_if_missing(false));
assert!(
err.is_err(),
"opening an encrypted DB without a key must fail"
);
}
#[test]
fn key_supplied_for_plaintext_db_errors() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("p.chisel");
{
let mut db = Chisel::open(&path, Options::default()).unwrap();
db.begin().unwrap();
db.commit().unwrap();
}
let err = Chisel::open(
&path,
Options::default()
.encryption_key(raw_key(0x11))
.create_if_missing(false),
);
assert!(err.is_err(), "supplying a key to a plaintext DB must fail");
}
#[test]
fn plaintext_db_round_trips_without_key() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("plain.chisel");
let handle;
{
let mut db = Chisel::open(&path, Options::default()).unwrap();
db.begin().unwrap();
handle = db.allocate(b"plain text").unwrap();
db.commit().unwrap();
}
{
let db = Chisel::open(&path, Options::default().create_if_missing(false)).unwrap();
assert_eq!(db.read(handle).unwrap(), b"plain text");
}
}
#[test]
fn passphrase_key_round_trip() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("passphrase.chisel");
let pass = || Key::Passphrase(Zeroizing::new("correct horse battery staple".to_string()));
let handle;
{
let mut db = Chisel::open(&path, Options::default().encryption_key(pass())).unwrap();
db.begin().unwrap();
handle = db.allocate(b"secret").unwrap();
db.commit().unwrap();
}
{
let db = Chisel::open(
&path,
Options::default()
.encryption_key(pass())
.create_if_missing(false),
)
.unwrap();
assert_eq!(db.read(handle).unwrap(), b"secret");
}
}
#[test]
fn open_encrypted_db_with_no_commits_uses_correct_key() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("fresh.chisel");
{
let _db = Chisel::open(&path, Options::default().encryption_key(raw_key(0x42))).unwrap();
}
let result = Chisel::open(
&path,
Options::default()
.encryption_key(raw_key(0x42))
.create_if_missing(false),
);
assert!(
result.is_ok(),
"correct key must open a never-committed encrypted DB; got: {:?}",
result.err()
);
}
#[test]
fn named_root_round_trips_through_encrypted_open() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("named.chisel");
let handle;
{
let mut db = Chisel::open(&path, Options::default().encryption_key(raw_key(0xAB))).unwrap();
db.begin().unwrap();
handle = db.allocate(b"payload").unwrap();
db.set_root_name("myroot", handle).unwrap();
db.commit().unwrap();
}
{
let db = Chisel::open(
&path,
Options::default()
.encryption_key(raw_key(0xAB))
.create_if_missing(false),
)
.unwrap();
let h = db.get_root_name("myroot").unwrap();
assert!(h.is_some(), "named root must survive close+reopen");
assert_eq!(db.read(h.unwrap()).unwrap(), b"payload");
}
}
#[test]
fn never_committed_encrypted_db_with_extra_superblocks_reopens() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("n4.chisel");
{
let _db = Chisel::open(
&path,
Options::default()
.encryption_key(raw_key(0x55))
.superblock_count(4),
)
.unwrap();
}
let reopened = Chisel::open(
&path,
Options::default()
.encryption_key(raw_key(0x55))
.create_if_missing(false),
);
assert!(
reopened.is_ok(),
"never-committed N=4 encrypted DB must reopen; got: {:?}",
reopened.err()
);
}
#[test]
fn multi_page_encrypted_value_round_trips() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("big.chisel");
let payload: Vec<u8> = (0..32 * 1024).map(|i| (i % 251) as u8).collect();
let handle;
{
let mut db = Chisel::open(&path, Options::default().encryption_key(raw_key(0x77))).unwrap();
db.begin().unwrap();
handle = db.allocate(&payload).unwrap();
db.commit().unwrap();
}
{
let db = Chisel::open(
&path,
Options::default()
.encryption_key(raw_key(0x77))
.create_if_missing(false),
)
.unwrap();
assert_eq!(
db.read(handle).unwrap(),
payload,
"multi-page encrypted value must survive close+reopen byte-for-byte"
);
}
}
#[test]
fn torn_slot_0_encrypted_db_recovers_via_sibling() {
const ENC_STRIDE: u64 = 8232;
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("torn.chisel");
let h1;
{
let mut db = Chisel::open(&path, Options::default().encryption_key(raw_key(0x99))).unwrap();
db.begin().unwrap();
h1 = db.allocate(b"survives the tear").unwrap();
db.commit().unwrap();
db.begin().unwrap();
let _h2 = db.allocate(b"second commit").unwrap();
db.commit().unwrap();
}
{
let mut f = std::fs::OpenOptions::new().write(true).open(&path).unwrap();
f.seek(SeekFrom::Start(0)).unwrap();
f.write_all(&[0u8; 8192]).unwrap();
f.sync_all().unwrap();
}
let len = std::fs::metadata(&path).unwrap().len();
assert_eq!(
len % ENC_STRIDE,
0,
"encrypted file length {len} must stay a multiple of {ENC_STRIDE}"
);
let db = Chisel::open(
&path,
Options::default()
.encryption_key(raw_key(0x99))
.create_if_missing(false),
)
.expect("correct key must recover a torn-slot-0 encrypted DB via its sibling");
assert_eq!(db.read(h1).unwrap(), b"survives the tear");
}