use pagedb::vfs::memory::MemVfs;
use pagedb::{Db, OpenOptions, PagedbError, RealmId};
const KEK: [u8; 32] = [0x11; 32];
const OTHER_KEK: [u8; 32] = [0x22; 32];
const REALM: RealmId = RealmId::new([0xAA; 16]);
const PAGE: usize = 4096;
async fn seeded_store() -> MemVfs {
let vfs = MemVfs::new();
let db = Db::open(vfs.clone(), KEK, PAGE, REALM, OpenOptions::default())
.await
.unwrap();
let mut writer = db.begin_write().await.unwrap();
writer.put(b"k", b"v").await.unwrap();
writer.commit().await.unwrap();
vfs
}
async fn assert_still_intact(vfs: MemVfs) {
let db = Db::open(vfs, KEK, PAGE, REALM, OpenOptions::default())
.await
.expect("the rightful parameters must still open the store");
let reader = db.begin_read().await.unwrap();
assert_eq!(
reader.get(b"k").await.unwrap().as_deref(),
Some(b"v".as_slice())
);
}
#[tokio::test(flavor = "current_thread")]
async fn a_wrong_kek_is_not_reported_as_corruption() {
let vfs = seeded_store().await;
let error = Db::open(vfs.clone(), OTHER_KEK, PAGE, REALM, OpenOptions::default())
.await
.err()
.expect("the wrong KEK must not open the store");
assert!(
matches!(error, PagedbError::KeyMismatch),
"a wrong key must be named as such, not as damage: {error:?}"
);
let rendered = error.to_string();
assert!(
rendered.contains("not modified") && rendered.contains("discard"),
"the message must tell the operator the store is intact: {rendered}"
);
assert_still_intact(vfs).await;
}
#[tokio::test(flavor = "current_thread")]
async fn a_wrong_page_size_names_the_stored_size() {
let vfs = seeded_store().await;
for supplied in [8192usize, 16384, 65536] {
let error = Db::open(vfs.clone(), KEK, supplied, REALM, OpenOptions::default())
.await
.err()
.expect("a mismatched page size must not open the store");
assert!(
matches!(
error,
PagedbError::PageSizeMismatch { stored, supplied: got }
if stored == PAGE && got == supplied
),
"expected the stored and supplied sizes to be named: {error:?}"
);
}
assert_still_intact(vfs).await;
}
#[tokio::test(flavor = "current_thread")]
async fn a_wrong_page_size_is_named_in_both_directions() {
let vfs = MemVfs::new();
{
let db = Db::open(vfs.clone(), KEK, 16384, REALM, OpenOptions::default())
.await
.unwrap();
let mut writer = db.begin_write().await.unwrap();
writer.put(b"k", b"v").await.unwrap();
writer.commit().await.unwrap();
}
let error = Db::open(vfs.clone(), KEK, 4096, REALM, OpenOptions::default())
.await
.err()
.expect("a too-small page size must not open the store");
assert!(
matches!(
error,
PagedbError::PageSizeMismatch {
stored: 16384,
supplied: 4096
}
),
"{error:?}"
);
let db = Db::open(vfs, KEK, 16384, REALM, OpenOptions::default())
.await
.unwrap();
let reader = db.begin_read().await.unwrap();
assert_eq!(
reader.get(b"k").await.unwrap().as_deref(),
Some(b"v".as_slice())
);
}
#[tokio::test(flavor = "current_thread")]
async fn read_only_modes_classify_the_same_mistakes() {
let vfs = seeded_store().await;
let error = Db::open_read_only(vfs.clone(), OTHER_KEK, PAGE, REALM, OpenOptions::default())
.await
.err()
.expect("the wrong KEK must not open a read-only handle");
assert!(matches!(error, PagedbError::KeyMismatch), "{error:?}");
let error = Db::open_read_only(vfs.clone(), KEK, 8192, REALM, OpenOptions::default())
.await
.err()
.expect("a mismatched page size must not open a read-only handle");
assert!(
matches!(
error,
PagedbError::PageSizeMismatch {
stored: 4096,
supplied: 8192
}
),
"{error:?}"
);
let error = Db::open_read_only(
vfs.clone(),
KEK,
PAGE,
RealmId::new([0xBB; 16]),
OpenOptions::default(),
)
.await
.err()
.expect("a foreign realm must not open a read-only handle");
assert!(
matches!(error, PagedbError::RealmMismatch { .. }),
"{error:?}"
);
assert_still_intact(vfs).await;
}
#[tokio::test(flavor = "current_thread")]
async fn a_store_from_another_format_version_is_told_to_migrate() {
use pagedb::vfs::OpenMode;
use pagedb::vfs::{Vfs, VfsFile};
let vfs = seeded_store().await;
{
let mut file = vfs.open("/main.db", OpenMode::ReadWrite).await.unwrap();
for slot in 0..2u64 {
file.write_at(slot * PAGE as u64 + 8, &99u16.to_le_bytes())
.await
.unwrap();
}
file.sync().await.unwrap();
}
let error = Db::open(vfs.clone(), KEK, PAGE, REALM, OpenOptions::default())
.await
.err()
.expect("a store at an unknown format version must not open");
assert!(
matches!(
error,
PagedbError::FormatVersionUnsupported {
stored: 99,
supported: 1
}
),
"expected a version verdict rather than a key verdict: {error:?}"
);
let rendered = error.to_string();
assert!(
rendered.contains("not modified") && rendered.contains("migration"),
"the message must point at migration, not discarding: {rendered}"
);
let error = Db::open_read_only(vfs, KEK, PAGE, REALM, OpenOptions::default())
.await
.err()
.expect("a read-only handle must refuse it too");
assert!(
matches!(error, PagedbError::FormatVersionUnsupported { .. }),
"{error:?}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn bytes_that_are_not_a_pagedb_header_are_still_corruption() {
use pagedb::vfs::OpenMode;
use pagedb::vfs::{Vfs, VfsFile};
let vfs = seeded_store().await;
{
let mut file = vfs.open("/main.db", OpenMode::ReadWrite).await.unwrap();
for slot in 0..2u64 {
file.write_at(slot * PAGE as u64, b"NOTPGDB!")
.await
.unwrap();
}
file.sync().await.unwrap();
}
let error = Db::open(vfs, KEK, PAGE, REALM, OpenOptions::default())
.await
.err()
.expect("a destroyed header must not open");
assert!(
matches!(error, PagedbError::Corruption(_)),
"a store that no longer claims to be pagedb is damage, not a key \
problem: {error:?}"
);
}