#![cfg(all(feature = "encryption", feature = "test-faults"))]
use std::sync::Arc;
use armdb::armour::Db;
use armdb::{CollectionMeta, Config, NoHook, RapiraCodec, TtlHook, TxConfig, TypedMap};
use armour_core::GetType;
use rapira::Rapira;
type Id = [u8; 8];
fn id(n: u64) -> Id {
n.to_be_bytes()
}
#[derive(Clone, Debug, PartialEq, Eq, GetType, Rapira)]
struct Secret {
text: String,
}
impl CollectionMeta for Secret {
type SelfId = Id;
const NAME: &'static str = "secrets";
}
const DATA_KEY: [u8; 32] = [0x11; 32];
const LOG_KEY: [u8; 32] = [0x22; 32];
fn open_db(dir: &std::path::Path, log_key: Option<[u8; 32]>) -> Arc<Db> {
Arc::new(
Db::open_with_tx_config(
dir,
TxConfig {
log_encryption_key: log_key,
..TxConfig::default()
},
)
.unwrap(),
)
}
fn open_secrets(db: &Arc<Db>) -> Arc<TypedMap<Id, Secret, RapiraCodec, TtlHook<Secret, NoHook>>> {
let mut cfg = Config::test();
cfg.encryption_key = Some(DATA_KEY);
db.open_typed_map::<Secret, RapiraCodec, _>(cfg, NoHook, &[])
.unwrap()
}
#[test]
fn tx_encrypted_commit_replays_after_a_crash_and_keeps_the_log_sealed() {
let dir = tempfile::tempdir().unwrap();
{
let db = open_db(dir.path(), Some(LOG_KEY));
let secrets = open_secrets(&db);
Db::tx_debug_skip_apply(true);
db.tx()
.put(
&secrets,
&id(2),
&Secret {
text: "top secret".into(),
},
)
.unwrap()
.durable()
.commit()
.unwrap();
Db::tx_debug_skip_apply(false);
assert!(secrets.get(&id(2)).is_none());
db.close().unwrap();
}
let seg = std::fs::read(dir.path().join("txlog").join("000001.log")).unwrap();
assert!(
seg.windows(10).all(|w| w != b"top secret"),
"the transaction log stored an encrypted collection's value in the clear"
);
let db = open_db(dir.path(), Some(LOG_KEY));
let secrets = open_secrets(&db);
assert_eq!(
secrets.get(&id(2)).map(|s| s.text.clone()),
Some("top secret".to_string()),
"the pending record replays through the envelope"
);
}
#[test]
fn tx_encrypted_log_fails_closed_without_the_key() {
let dir = tempfile::tempdir().unwrap();
{
let db = open_db(dir.path(), Some(LOG_KEY));
let secrets = open_secrets(&db);
Db::tx_debug_skip_apply(true);
db.tx()
.put(&secrets, &id(3), &Secret { text: "x".into() })
.unwrap()
.durable()
.commit()
.unwrap();
Db::tx_debug_skip_apply(false);
db.close().unwrap();
}
let err = Db::open_with_tx_config(dir.path(), TxConfig::default())
.err()
.expect("open without log key must fail");
assert!(
matches!(
err,
armdb::DbError::EncryptionError(_) | armdb::DbError::FormatMismatch(_)
),
"{err}"
);
let err = Db::open_with_tx_config(
dir.path(),
TxConfig {
log_encryption_key: Some([0x33; 32]),
..TxConfig::default()
},
)
.err()
.expect("open with wrong log key must fail");
assert!(matches!(err, armdb::DbError::EncryptionError(_)), "{err}");
}