pub(crate) const VALUE_TYPE_VALUE: u8 = 1;
pub(crate) const VALUE_TYPE_DELETION: u8 = 0;
pub(crate) const VALUE_TYPE_MERGE: u8 = 2;
pub(crate) const INTERNAL_KEY_SUFFIX_LEN: usize = 9;
pub(crate) fn encode_internal_key(user_key: &[u8], seq: u64, value_type: u8) -> Vec<u8> {
let mut key = Vec::with_capacity(user_key.len() + INTERNAL_KEY_SUFFIX_LEN);
key.extend_from_slice(user_key);
key.extend_from_slice(&(!seq).to_be_bytes());
key.push(value_type);
key
}
pub(crate) fn decode_internal_key(internal_key: &[u8]) -> (&[u8], u64, u8) {
let len = internal_key.len();
let value_type = internal_key[len - 1];
let seq_bytes: [u8; 8] = internal_key[len - 9..len - 1].try_into().unwrap();
let seq = !u64::from_be_bytes(seq_bytes);
let user_key = &internal_key[..len - 9];
(user_key, seq, value_type)
}
pub(crate) fn user_key_of(internal_key: &[u8]) -> &[u8] {
&internal_key[..internal_key.len() - INTERNAL_KEY_SUFFIX_LEN]
}
pub(crate) fn compare_internal_keys(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
if b.len() < INTERNAL_KEY_SUFFIX_LEN {
return a.cmp(b);
}
let split = b.len() - INTERNAL_KEY_SUFFIX_LEN;
compare_internal_split(a, &b[..split], &b[split..])
}
pub(crate) fn compare_internal_split(
a: &[u8],
b_user_key: &[u8],
b_trailer: &[u8],
) -> std::cmp::Ordering {
if a.len() < INTERNAL_KEY_SUFFIX_LEN {
return raw_cmp_split(a, b_user_key, b_trailer);
}
let a_uk = user_key_of(a);
match a_uk.cmp(b_user_key) {
std::cmp::Ordering::Equal => {
a[a_uk.len()..].cmp(b_trailer)
}
ord => ord,
}
}
fn raw_cmp_split(a: &[u8], b1: &[u8], b2: &[u8]) -> std::cmp::Ordering {
let head = a.len().min(b1.len());
match a[..head].cmp(&b1[..head]) {
std::cmp::Ordering::Equal => {}
ord => return ord,
}
if b1.len() > head {
return std::cmp::Ordering::Less;
}
let rest = &a[head..];
let tail = rest.len().min(b2.len());
match rest[..tail].cmp(&b2[..tail]) {
std::cmp::Ordering::Equal => {}
ord => return ord,
}
a.len().cmp(&(b1.len() + b2.len()))
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn roundtrip() {
let ik = encode_internal_key(b"hello", 42, VALUE_TYPE_VALUE);
let (uk, seq, vt) = decode_internal_key(&ik);
assert_eq!(uk, b"hello");
assert_eq!(seq, 42);
assert_eq!(vt, VALUE_TYPE_VALUE);
}
#[test]
fn newer_seq_sorts_first() {
let older = encode_internal_key(b"k", 5, VALUE_TYPE_VALUE);
let newer = encode_internal_key(b"k", 10, VALUE_TYPE_VALUE);
assert!(newer < older, "higher seq must sort before lower seq");
}
#[test]
fn search_key_matches_first_visible() {
let seq_10 = encode_internal_key(b"k", 10, VALUE_TYPE_VALUE);
let seq_5 = encode_internal_key(b"k", 5, VALUE_TYPE_VALUE);
let seq_3 = encode_internal_key(b"k", 3, VALUE_TYPE_VALUE);
let probe = encode_internal_key(b"k", 7, VALUE_TYPE_DELETION);
assert!(seq_10 < probe);
assert!(seq_5 >= probe);
assert!(seq_3 >= probe);
}
#[test]
fn user_key_of_strips_suffix() {
let ik = encode_internal_key(b"hello", 42, VALUE_TYPE_VALUE);
assert_eq!(user_key_of(&ik), b"hello");
}
#[test]
fn compare_correctly_orders_prefix_keys() {
let ab_high = encode_internal_key(b"ab", u64::MAX, VALUE_TYPE_VALUE);
let ab_low = encode_internal_key(b"ab", 0, VALUE_TYPE_VALUE);
let abc_high = encode_internal_key(b"abc", u64::MAX, VALUE_TYPE_VALUE);
assert!(compare_internal_keys(&ab_high, &abc_high).is_lt());
assert!(compare_internal_keys(&ab_low, &abc_high).is_lt());
assert!(compare_internal_keys(&abc_high, &ab_high).is_gt());
}
#[test]
fn compare_falls_back_to_raw_for_short_keys() {
let a = b"ab";
let b = b"ac";
assert!(compare_internal_keys(a, b).is_lt());
assert!(compare_internal_keys(b, a).is_gt());
assert!(compare_internal_keys(a, a).is_eq());
}
#[test]
fn every_value_type_round_trips() {
for vt in [VALUE_TYPE_VALUE, VALUE_TYPE_DELETION, VALUE_TYPE_MERGE] {
let ik = encode_internal_key(b"k", 3, vt);
let (_, _, decoded_vt) = decode_internal_key(&ik);
assert_eq!(decoded_vt, vt);
}
}
#[test]
fn search_key_at_u64_max_sorts_after_any_stored_seq() {
let probe = encode_internal_key(b"k", u64::MAX, VALUE_TYPE_DELETION);
let seq_1 = encode_internal_key(b"k", 1, VALUE_TYPE_VALUE);
let seq_huge = encode_internal_key(b"k", u64::MAX - 1, VALUE_TYPE_VALUE);
assert!(compare_internal_keys(&probe, &seq_1).is_le());
assert!(compare_internal_keys(&probe, &seq_huge).is_le());
}
#[test]
fn short_key_from_a_tampered_sstable_is_rejected_as_corruption() {
fn varint(buf: &mut Vec<u8>, mut v: u64) {
while v >= 0x80 {
buf.push((v as u8) | 0x80);
v >>= 7;
}
buf.push(v as u8);
}
let dir = tempfile::tempdir().unwrap();
let opts = crate::Options {
block_size: 64 * 1024,
compression: crate::CompressionType::None,
..crate::Options::default()
};
{
let db = crate::Db::open(dir.path(), opts.clone()).unwrap();
for i in 0..8 {
db.put(format!("k{i}").as_bytes(), b"v").unwrap();
}
db.compact_range(None, None).unwrap();
}
let sst_dir = dir.path().join("sst");
let sst = std::fs::read_dir(&sst_dir)
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.path())
.find(|p| p.extension().and_then(|s| s.to_str()) == Some("sst"))
.expect("one sst file");
let mut bytes = std::fs::read(&sst).unwrap();
let f = &bytes[bytes.len() - 64..];
let rd = |i: usize| u64::from_le_bytes(f[i * 8..i * 8 + 8].try_into().unwrap()) as usize;
let data_end = [rd(0), rd(2), rd(4)]
.into_iter()
.filter(|o| *o > 0)
.min()
.expect("a section follows the data blocks");
let frame_len = data_end;
let payload_len = frame_len - 5;
let mut value_len = payload_len - 14;
let mut payload = Vec::new();
loop {
payload.clear();
varint(&mut payload, 0);
varint(&mut payload, 3);
varint(&mut payload, value_len as u64);
payload.extend_from_slice(b"abc");
payload.resize(payload.len() + value_len, 0u8);
payload.extend_from_slice(&0u32.to_le_bytes());
payload.extend_from_slice(&1u32.to_le_bytes());
match payload.len().cmp(&payload_len) {
std::cmp::Ordering::Equal => break,
std::cmp::Ordering::Less => value_len += payload_len - payload.len(),
std::cmp::Ordering::Greater => value_len -= payload.len() - payload_len,
}
}
let checksum = crate::engine::checksum::sst_block(0, &payload);
bytes[0] = 0;
bytes[1..1 + payload.len()].copy_from_slice(&payload);
bytes[1 + payload.len()..frame_len].copy_from_slice(&checksum.to_le_bytes());
std::fs::write(&sst, &bytes).unwrap();
assert!(crate::engine::block::Block::decode(payload.clone()).is_ok());
match crate::engine::block::Block::decode_data_block(payload) {
Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::InvalidData),
Ok(_) => panic!("a short key must not decode as a data block"),
}
match crate::Db::open(dir.path(), opts) {
Err(e) => assert!(matches!(e, crate::Error::Corruption(_)), "{e:?}"),
Ok(db) => {
let mut it = db.iter();
it.seek_to_first();
let mut seen = 0;
while it.valid() && seen < 1000 {
let _ = it.key();
it.next();
seen += 1;
}
assert!(it.status().is_err() || seen < 1000);
assert!(db.get(b"k0").is_err() || db.get(b"k0").is_ok());
}
}
}
proptest! {
#[test]
fn split_comparator_matches_the_concatenated_one(
a_key in proptest::collection::vec(any::<u8>(), 0..24),
a_seq in any::<u64>(),
b_key in proptest::collection::vec(any::<u8>(), 0..24),
b_seq in any::<u64>(),
a_vt in 0u8..3,
b_vt in 0u8..3,
) {
let a = encode_internal_key(&a_key, a_seq, a_vt);
let b = encode_internal_key(&b_key, b_seq, b_vt);
let mut trailer = [0u8; INTERNAL_KEY_SUFFIX_LEN];
trailer[..8].copy_from_slice(&(!b_seq).to_be_bytes());
trailer[8] = b_vt;
prop_assert_eq!(
compare_internal_split(&a, &b_key, &trailer),
compare_internal_keys(&a, &b)
);
}
#[test]
fn split_comparator_matches_on_short_keys(
a in proptest::collection::vec(any::<u8>(), 0..12),
b_key in proptest::collection::vec(any::<u8>(), 0..12),
b_seq in any::<u64>(),
) {
let b = encode_internal_key(&b_key, b_seq, VALUE_TYPE_VALUE);
let mut trailer = [0u8; INTERNAL_KEY_SUFFIX_LEN];
trailer[..8].copy_from_slice(&(!b_seq).to_be_bytes());
trailer[8] = VALUE_TYPE_VALUE;
prop_assert_eq!(
compare_internal_split(&a, &b_key, &trailer),
compare_internal_keys(&a, &b)
);
}
}
}