1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//! Shared `#[cfg(test)]` fault-injection helpers for the read/mutate error
//! contract tests (spec 26-06-13-armdb-var-read-error-contract).
#[cfg(feature = "encryption")]
use std::path::Path;
use crate::codec::Codec;
use crate::error::{DbError, DbResult};
/// Sentinel `u64` whose bytes are *readable* but which `PoisonCodec` refuses to
/// *decode* — simulates a value corrupted/drifted at the codec layer.
pub(crate) const POISON: u64 = 0xDEAD_BEEF_DEAD_BEEF;
/// Test codec for `u64`: stores the little-endian bytes verbatim, but
/// `decode_from` returns `CorruptedEntry` for the `POISON` sentinel (and for any
/// non-8-byte input). Lets a test write a value whose bytes read back fine yet
/// fail to decode.
#[derive(Clone, Default)]
pub(crate) struct PoisonCodec;
impl Codec<u64> for PoisonCodec {
fn encode_to(&self, value: &u64, buf: &mut Vec<u8>) -> DbResult<()> {
buf.clear();
buf.extend_from_slice(&value.to_le_bytes());
Ok(())
}
fn decode_from(&self, bytes: &[u8]) -> DbResult<u64> {
let arr: [u8; 8] = bytes
.try_into()
.map_err(|_| DbError::CorruptedEntry { offset: 0 })?;
let v = u64::from_le_bytes(arr);
if v == POISON {
return Err(DbError::CorruptedEntry { offset: 0 });
}
Ok(v)
}
fn size(&self, _value: &u64) -> usize {
8
}
}
/// A value large enough (16 KiB) to force the on-disk read path after a file
/// rotation, matching the existing encrypted large-value tests.
/// Only consumed by the `encryption`-gated raw read-fault tests.
#[cfg(feature = "encryption")]
pub(crate) fn big_value(fill: u8) -> Vec<u8> {
vec![fill; 16 * 1024]
}
/// Corrupt every AEAD tag file (`*.tags`) in `shard_dir` by flipping all bytes.
/// With `encryption` enabled, each data page has a sibling tag; flipping tags
/// makes the next decrypt of any page fail with `EncryptionError`. Call after a
/// rotation (so the value sits in an immutable on-disk file) and with the block
/// cache disabled (the default `Config::test()`), so the next read decrypts from
/// disk. Only consumed by the `encryption`-gated raw read-fault tests.
#[cfg(feature = "encryption")]
pub(crate) fn corrupt_tags(shard_dir: &Path) {
let mut found = false;
for entry in std::fs::read_dir(shard_dir).expect("read shard dir") {
let path = entry.expect("dir entry").path();
if path.extension().and_then(|e| e.to_str()) == Some("tags") {
let mut bytes = std::fs::read(&path).expect("read tags file");
if bytes.is_empty() {
continue;
}
for b in bytes.iter_mut() {
*b ^= 0xFF;
}
std::fs::write(&path, &bytes).expect("write tags file");
found = true;
}
}
assert!(found, "no non-empty .tags file found in {shard_dir:?}");
}