use crc32fast::Hasher;
use std::fmt::Write;
use std::hash::Hash;
use std::iter::zip;
fn crc32(bit: &[u8]) -> u32 {
let mut hasher = Hasher::new();
hasher.update(bit);
hasher.finalize()
}
pub type SerialisedKey = Vec<u8>;
pub type SearchKeyFn = fn(&Key) -> SerializedKey;
pub fn search_key_plain(key: &Key) -> SerializedKey {
key.0.join(&b'\x00')
}
pub fn search_key_16(key: &Key) -> SerializedKey {
let mut result = String::new();
for bit in key.iter() {
write!(&mut result, "{:08X}\x00", crc32(bit)).unwrap();
}
result.pop();
result.as_bytes().to_vec()
}
pub fn search_key_255(key: &Key) -> SerializedKey {
let mut result = vec![];
for bit in key.iter() {
let crc = crc32(bit);
let crc_bytes = crc.to_be_bytes();
result.extend(crc_bytes);
result.push(0x00);
}
result.pop();
result
.iter()
.map(|b| if *b == 0x0A { b'_' } else { *b })
.collect()
}
pub fn bytes_to_text_key(data: &[u8]) -> Result<(&[u8], &[u8]), String> {
let sections: Vec<&[u8]> = data.split(|&byte| byte == b'\n').collect();
let delimiter_position = sections[0].windows(2).position(|window| window == b": ");
if delimiter_position.is_none() {
return Err("Invalid key file".to_string());
}
let (_kind, file_id) = sections[0].split_at(delimiter_position.unwrap() + 2);
Ok((file_id, sections[3]))
}
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
pub struct Key(Vec<Vec<u8>>);
impl From<Vec<Vec<u8>>> for Key {
fn from(v: Vec<Vec<u8>>) -> Self {
Key(v)
}
}
impl Key {
pub fn serialize(&self) -> SerializedKey {
let mut result = vec![];
for bit in self.0.iter() {
result.extend(bit);
result.push(0x00);
}
result.pop();
result
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
self.0.iter().map(|v| v.as_slice())
}
}
impl std::ops::Index<usize> for Key {
type Output = Vec<u8>;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl std::fmt::Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let mut first = true;
for bit in &self.0 {
if !first {
write!(f, "/")?;
}
first = false;
write!(f, "{}", String::from_utf8_lossy(bit))?;
}
Ok(())
}
}
pub type SerializedKey = Vec<u8>;
pub type Value = Vec<u8>;
#[derive(Debug)]
pub enum Error {
InconsistentDeltaDelta(Vec<(Option<Key>, Option<Key>, Value)>, String),
DeserializeError(String),
}
impl From<std::num::ParseIntError> for Error {
fn from(e: std::num::ParseIntError) -> Self {
Error::DeserializeError(format!("Failed to parse int: {}", e))
}
}
pub fn common_prefix_pair<'b>(prefix: &[u8], key: &'b [u8]) -> &'b [u8] {
if key.starts_with(prefix) {
return &key[..prefix.len()];
}
let mut p = 0;
for (left, right) in zip(prefix, key) {
if left != right {
break;
}
p += 1;
}
let p = p as usize;
&key[..p]
}
#[test]
fn test_common_prefix_pair() {
assert_eq!(common_prefix_pair(b"abc", b"abc"), b"abc");
assert_eq!(common_prefix_pair(b"abc", b"abcd"), b"abc");
assert_eq!(common_prefix_pair(b"abc", b"ab"), b"ab");
assert_eq!(common_prefix_pair(b"abc", b"bbd"), b"");
assert_eq!(common_prefix_pair(b"", b"bbc"), b"");
assert_eq!(common_prefix_pair(b"abc", b""), b"");
}
pub fn common_prefix_many<'a>(mut keys: impl Iterator<Item = &'a [u8]> + 'a) -> Option<&'a [u8]> {
let mut cp = keys.next()?;
for key in keys {
cp = common_prefix_pair(cp, key);
if cp.is_empty() {
break;
}
}
Some(cp)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedLeafNode {
pub maximum_size: usize,
pub key_width: usize,
pub length: usize,
pub common_serialised_prefix: Vec<u8>,
pub items: Vec<(Vec<Vec<u8>>, Vec<u8>)>,
pub raw_size: usize,
}
pub fn deserialise_leaf_node(data: &[u8]) -> Result<ParsedLeafNode, Error> {
let mut lines: Vec<&[u8]> = data.split(|&b| b == b'\n').collect();
let trailing = lines
.pop()
.ok_or_else(|| Error::DeserializeError("empty leaf node body".into()))?;
if !trailing.is_empty() {
return Err(Error::DeserializeError(
"leaf node did not end with final newline".into(),
));
}
if lines.len() < 5 {
return Err(Error::DeserializeError(
"leaf node truncated before item lines".into(),
));
}
if lines[0] != b"chkleaf:" {
return Err(Error::DeserializeError("not a serialised leaf node".into()));
}
let maximum_size = parse_decimal(lines[1], "maximum_size")?;
let width = parse_decimal(lines[2], "key_width")?;
let length = parse_decimal(lines[3], "length")?;
let prefix = lines[4];
let mut items: Vec<(Vec<Vec<u8>>, Vec<u8>)> = Vec::with_capacity(length);
let mut pos = 5usize;
while pos < lines.len() {
let mut full = Vec::with_capacity(prefix.len() + lines[pos].len());
full.extend_from_slice(prefix);
full.extend_from_slice(lines[pos]);
pos += 1;
let mut elements: Vec<Vec<u8>> = full.split(|&b| b == 0).map(|s| s.to_vec()).collect();
if elements.len() != width + 1 {
return Err(Error::DeserializeError(format!(
"incorrect number of elements ({} vs {}) for leaf line",
elements.len(),
width + 1
)));
}
let count_bytes = elements.pop().expect("just checked non-empty");
let num_value_lines = parse_decimal(&count_bytes, "value line count")?;
if pos + num_value_lines > lines.len() {
return Err(Error::DeserializeError(
"leaf node value line runs past end of body".into(),
));
}
let value_lines = &lines[pos..pos + num_value_lines];
pos += num_value_lines;
let value_len =
value_lines.iter().map(|l| l.len()).sum::<usize>() + num_value_lines.saturating_sub(1);
let mut value = Vec::with_capacity(value_len);
for (i, line) in value_lines.iter().enumerate() {
if i > 0 {
value.push(b'\n');
}
value.extend_from_slice(line);
}
items.push((elements, value));
}
if items.len() != length {
return Err(Error::DeserializeError(format!(
"item count ({}) mismatch: found {}",
length,
items.len()
)));
}
let suffix_bytes: usize = lines[5..].iter().map(|l| l.len()).sum();
let raw_size = suffix_bytes + length * prefix.len() + (lines.len() - 5);
Ok(ParsedLeafNode {
maximum_size,
key_width: width,
length,
common_serialised_prefix: prefix.to_vec(),
items,
raw_size,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedInternalNode {
pub maximum_size: usize,
pub key_width: usize,
pub length: usize,
pub search_prefix: Vec<u8>,
pub items: Vec<(Vec<u8>, Vec<u8>)>,
pub node_width: usize,
}
pub fn deserialise_internal_node(data: &[u8]) -> Result<ParsedInternalNode, Error> {
let mut lines: Vec<&[u8]> = data.split(|&b| b == b'\n').collect();
let trailing = lines
.pop()
.ok_or_else(|| Error::DeserializeError("empty internal node body".into()))?;
if !trailing.is_empty() {
return Err(Error::DeserializeError("last line must be ''".into()));
}
if lines.len() < 5 {
return Err(Error::DeserializeError(
"internal node truncated before item lines".into(),
));
}
if lines[0] != b"chknode:" {
return Err(Error::DeserializeError(
"not a serialised internal node".into(),
));
}
let maximum_size = parse_decimal(lines[1], "maximum_size")?;
let width = parse_decimal(lines[2], "key_width")?;
let length = parse_decimal(lines[3], "length")?;
let common_prefix = lines[4];
let mut items: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
let mut last_prefix_len = 0usize;
for suffix in &lines[5..] {
let mut full = Vec::with_capacity(common_prefix.len() + suffix.len());
full.extend_from_slice(common_prefix);
full.extend_from_slice(suffix);
let split_at = full
.iter()
.rposition(|&b| b == 0)
.ok_or_else(|| Error::DeserializeError("internal node line missing NUL".into()))?;
let prefix = full[..split_at].to_vec();
let flat_key = full[split_at + 1..].to_vec();
last_prefix_len = prefix.len();
items.push((prefix, flat_key));
}
if items.is_empty() {
return Err(Error::DeserializeError(
"internal node contained no items".into(),
));
}
Ok(ParsedInternalNode {
maximum_size,
key_width: width,
length,
search_prefix: common_prefix.to_vec(),
items,
node_width: last_prefix_len,
})
}
fn parse_decimal(bytes: &[u8], what: &str) -> Result<usize, Error> {
std::str::from_utf8(bytes)
.ok()
.and_then(|s| s.parse::<usize>().ok())
.ok_or_else(|| Error::DeserializeError(format!("invalid {}: {:?}", what, bytes)))
}
#[test]
fn test_common_prefix_many() {
assert_eq!(
common_prefix_many(vec![&b"abc"[..], &b"abc"[..]].into_iter()),
Some(&b"abc"[..])
);
assert_eq!(
common_prefix_many(vec![&b"abc"[..], &b"abcd"[..]].into_iter()),
Some(&b"abc"[..])
);
assert_eq!(
common_prefix_many(vec![&b"abc"[..], &b"ab"[..]].into_iter()),
Some(&b"ab"[..])
);
assert_eq!(
common_prefix_many(vec![&b"abc"[..], &b"bbd"[..]].into_iter()),
Some(&b""[..])
);
assert_eq!(
common_prefix_many(vec![&b"abcd"[..], &b"abc"[..], &b"abc"[..]].into_iter()),
Some(&b"abc"[..])
);
assert_eq!(common_prefix_many(vec![].into_iter()), None);
}
#[cfg(test)]
mod tests {
use super::*;
fn key(parts: &[&[u8]]) -> Key {
Key::from(parts.iter().map(|p| p.to_vec()).collect::<Vec<_>>())
}
#[test]
fn search_key_plain_joins_with_nul() {
assert_eq!(
search_key_plain(&key(&[b"foo", b"bar"])),
b"foo\x00bar".to_vec()
);
assert_eq!(search_key_plain(&key(&[b"only"])), b"only".to_vec());
}
#[test]
fn search_key_16_is_uppercase_hex() {
let out = search_key_16(&key(&[b"hello"]));
let s = std::str::from_utf8(&out).unwrap();
assert_eq!(s.len(), 8);
assert!(s
.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_ascii_lowercase()));
}
#[test]
fn search_key_16_joins_multi_element_keys_with_nul() {
let out = search_key_16(&key(&[b"a", b"b"]));
assert_eq!(out.len(), 17);
assert_eq!(out[8], b'\x00');
}
#[test]
fn search_key_255_uses_raw_be_bytes_with_lf_replaced() {
let out = search_key_255(&key(&[b"hello"]));
assert_eq!(out.len(), 4);
assert!(!out.contains(&b'\n'));
}
#[test]
fn search_key_255_replaces_lf_with_underscore() {
for input in [b"abc".as_slice(), b"x", b"y"] {
let out = search_key_255(&key(&[input]));
assert!(!out.contains(&b'\n'));
}
}
#[test]
fn search_key_255_multi_element_keys_use_nul_separator() {
let out = search_key_255(&key(&[b"a", b"b"]));
assert_eq!(out.len(), 9);
assert_eq!(out[4], b'\x00');
}
#[test]
fn bytes_to_text_key_parses_file_record() {
let bytes = b"file: file-id\nparent-id\nname\nrevision-id\n\
da39a3ee5e6b4b0d3255bfef95601890afd80709\n100\nN";
let (file_id, revision_id) = bytes_to_text_key(bytes).unwrap();
assert_eq!(file_id, b"file-id");
assert_eq!(revision_id, b"revision-id");
}
#[test]
fn bytes_to_text_key_rejects_missing_separator() {
let bytes = b"file:file-id\nparent-id\nname\nrevision-id\n\
da39a3ee5e6b4b0d3255bfef95601890afd80709\n100\nN";
assert!(bytes_to_text_key(bytes).is_err());
}
#[test]
fn key_serialize_joins_parts_with_nul() {
assert_eq!(key(&[b"foo", b"bar"]).serialize(), b"foo\x00bar".to_vec());
assert_eq!(key(&[b"alone"]).serialize(), b"alone".to_vec());
}
#[test]
fn key_len_returns_part_count() {
assert_eq!(key(&[b"foo", b"bar"]).len(), 2);
assert_eq!(key(&[b"a"]).len(), 1);
}
const LEAF_FIXTURE: &[u8] = b"chkleaf:\n100\n1\n2\nalph\n2\x002\nv2\nv2line2\na\x001\nv1\n";
#[test]
fn deserialise_leaf_fixture_items_match() {
let p = deserialise_leaf_node(LEAF_FIXTURE).unwrap();
assert_eq!(p.maximum_size, 100);
assert_eq!(p.key_width, 1);
assert_eq!(p.length, 2);
assert_eq!(p.common_serialised_prefix, b"alph");
assert_eq!(p.items.len(), 2);
assert_eq!(p.items[0].0, vec![b"alph2".to_vec()]);
assert_eq!(p.items[0].1, b"v2\nv2line2");
assert_eq!(p.items[1].0, vec![b"alpha".to_vec()]);
assert_eq!(p.items[1].1, b"v1");
}
#[test]
fn deserialise_leaf_raw_size_matches_python_formula() {
let p = deserialise_leaf_node(LEAF_FIXTURE).unwrap();
assert_eq!(p.raw_size, 30);
}
#[test]
fn deserialise_leaf_empty_items() {
let data = b"chkleaf:\n100\n1\n0\n\n";
let p = deserialise_leaf_node(data).unwrap();
assert_eq!(p.length, 0);
assert!(p.items.is_empty());
assert_eq!(p.common_serialised_prefix, b"");
assert_eq!(p.raw_size, 0);
}
#[test]
fn deserialise_leaf_multi_element_key() {
let data = b"chkleaf:\n200\n2\n1\n\nkey1\x00sub\x001\nhello\n";
let p = deserialise_leaf_node(data).unwrap();
assert_eq!(p.key_width, 2);
assert_eq!(p.items.len(), 1);
assert_eq!(p.items[0].0, vec![b"key1".to_vec(), b"sub".to_vec()]);
assert_eq!(p.items[0].1, b"hello");
}
#[test]
fn deserialise_leaf_rejects_missing_trailing_newline() {
let data = b"chkleaf:\n100\n1\n0\n";
assert!(matches!(
deserialise_leaf_node(data),
Err(Error::DeserializeError(_))
));
}
#[test]
fn deserialise_leaf_rejects_bad_magic() {
let data = b"notaleaf:\n100\n1\n0\n\n";
assert!(matches!(
deserialise_leaf_node(data),
Err(Error::DeserializeError(_))
));
}
#[test]
fn deserialise_leaf_rejects_wrong_element_count() {
let data = b"chkleaf:\n100\n1\n1\n\nfoo\x00bar\x001\nval\n";
assert!(matches!(
deserialise_leaf_node(data),
Err(Error::DeserializeError(_))
));
}
#[test]
fn deserialise_leaf_rejects_length_mismatch() {
let data = b"chkleaf:\n100\n1\n2\n\nfoo\x001\nval\n";
assert!(matches!(
deserialise_leaf_node(data),
Err(Error::DeserializeError(_))
));
}
const INTERNAL_FIXTURE: &[u8] = b"chknode:\n200\n1\n2\npre\nbar\x00sha1:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\nfoo\x00sha1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n";
#[test]
fn deserialise_internal_fixture_items_match() {
let p = deserialise_internal_node(INTERNAL_FIXTURE).unwrap();
assert_eq!(p.maximum_size, 200);
assert_eq!(p.key_width, 1);
assert_eq!(p.length, 2);
assert_eq!(p.search_prefix, b"pre");
assert_eq!(p.items.len(), 2);
assert_eq!(p.items[0].0, b"prebar");
assert_eq!(
p.items[0].1,
b"sha1:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
);
assert_eq!(p.items[1].0, b"prefoo");
assert_eq!(
p.items[1].1,
b"sha1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
);
assert_eq!(p.node_width, b"prefoo".len());
}
#[test]
fn deserialise_internal_rejects_empty_items() {
let data = b"chknode:\n100\n1\n0\n\n";
assert!(matches!(
deserialise_internal_node(data),
Err(Error::DeserializeError(_))
));
}
#[test]
fn deserialise_internal_rejects_bad_magic() {
let data = b"notchk:\n100\n1\n1\n\nfoo\x00sha1:aaaa\n";
assert!(matches!(
deserialise_internal_node(data),
Err(Error::DeserializeError(_))
));
}
#[test]
fn deserialise_internal_rejects_missing_trailing_newline() {
let data = b"chknode:\n100\n1\n1\n\nfoo\x00sha1:aaaa";
assert!(matches!(
deserialise_internal_node(data),
Err(Error::DeserializeError(_))
));
}
#[test]
fn deserialise_internal_rejects_line_without_nul() {
let data = b"chknode:\n100\n1\n1\n\nfoobar\n";
assert!(matches!(
deserialise_internal_node(data),
Err(Error::DeserializeError(_))
));
}
}