use std::cmp::Ordering;
const SIZEOF_INT32: usize = 4;
const SIZEOF_INT16: usize = 2;
pub const KEY_VALUE_HEADER_SIZE: usize = SIZEOF_INT32 * 2;
#[derive(Debug, Clone)]
pub struct Key {
bytes: Vec<u8>,
offset: usize,
length: usize,
}
impl Key {
pub fn new(bytes: &[u8], offset: usize, length: usize) -> Self {
let end = offset.saturating_add(length).min(bytes.len());
let start = offset.min(end);
Self {
bytes: bytes[start..end].to_vec(),
offset: 0,
length,
}
}
pub fn from_bytes(bytes: Vec<u8>) -> Self {
let length = bytes.len();
Self {
bytes,
offset: 0,
length,
}
}
pub fn from_content(content: &[u8]) -> Option<Self> {
let length = i16::try_from(content.len()).ok()?;
let mut bytes = Vec::with_capacity(content.len() + SIZEOF_INT16);
bytes.extend_from_slice(&length.to_be_bytes());
bytes.extend_from_slice(content);
Some(Self::from_bytes(bytes))
}
fn content_offset(&self) -> usize {
self.offset + SIZEOF_INT16
}
pub fn content_length(&self) -> usize {
if self.bytes.len() < self.offset + SIZEOF_INT16 {
return 0;
}
let len_bytes = &self.bytes[self.offset..self.offset + SIZEOF_INT16];
i16::from_be_bytes([len_bytes[0], len_bytes[1]]) as usize
}
pub fn content(&self) -> &[u8] {
let start = self.content_offset();
let len = self.content_length();
if start + len > self.bytes.len() {
return &[];
}
&self.bytes[start..start + len]
}
pub fn content_as_str(&self) -> Result<&str, std::str::Utf8Error> {
std::str::from_utf8(self.content())
}
pub fn length(&self) -> usize {
self.length
}
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
}
impl PartialEq for Key {
fn eq(&self, other: &Self) -> bool {
self.content() == other.content()
}
}
impl Eq for Key {}
impl PartialOrd for Key {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Key {
fn cmp(&self, other: &Self) -> Ordering {
self.content().cmp(other.content())
}
}
impl std::hash::Hash for Key {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.content().hash(state);
}
}
impl std::fmt::Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.content_as_str() {
Ok(s) => write!(f, "Key{{{s}}}"),
Err(_) => write!(f, "Key{{<binary>}}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Utf8Key {
content: String,
}
impl Utf8Key {
pub fn new(s: impl Into<String>) -> Self {
Self { content: s.into() }
}
pub fn as_bytes(&self) -> &[u8] {
self.content.as_bytes()
}
pub fn as_str(&self) -> &str {
&self.content
}
}
impl PartialOrd for Utf8Key {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Utf8Key {
fn cmp(&self, other: &Self) -> Ordering {
self.content.as_bytes().cmp(other.content.as_bytes())
}
}
impl From<&str> for Utf8Key {
fn from(s: &str) -> Self {
Utf8Key::new(s)
}
}
impl From<String> for Utf8Key {
fn from(s: String) -> Self {
Utf8Key::new(s)
}
}
impl std::fmt::Display for Utf8Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Utf8Key{{{}}}", self.content)
}
}
#[derive(Debug, Clone)]
pub struct KeyValue {
bytes: Vec<u8>,
offset: usize,
key: Key,
key_length: usize,
value_length: usize,
}
impl KeyValue {
pub fn parse(bytes: &[u8], offset: usize) -> Self {
let key_length = i32::from_be_bytes([
bytes[offset],
bytes[offset + 1],
bytes[offset + 2],
bytes[offset + 3],
]) as usize;
let value_length = i32::from_be_bytes([
bytes[offset + 4],
bytes[offset + 5],
bytes[offset + 6],
bytes[offset + 7],
]) as usize;
let key_offset = offset + KEY_VALUE_HEADER_SIZE;
let key = Key::new(bytes, key_offset, key_length);
let record_end = offset
.saturating_add(KEY_VALUE_HEADER_SIZE)
.saturating_add(key_length)
.saturating_add(value_length)
.min(bytes.len());
let record_start = offset.min(record_end);
Self {
bytes: bytes[record_start..record_end].to_vec(),
offset: 0,
key,
key_length,
value_length,
}
}
pub fn key(&self) -> &Key {
&self.key
}
pub fn value(&self) -> &[u8] {
let value_offset = self.offset + KEY_VALUE_HEADER_SIZE + self.key_length;
&self.bytes[value_offset..value_offset + self.value_length]
}
pub fn record_size(&self) -> usize {
KEY_VALUE_HEADER_SIZE + self.key_length + self.value_length + 1
}
pub fn key_length(&self) -> usize {
self.key_length
}
pub fn value_length(&self) -> usize {
self.value_length
}
}
impl std::fmt::Display for KeyValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "KeyValue{{key={}}}", self.key)
}
}
pub fn compare_keys(key: &Key, lookup: &Utf8Key) -> Ordering {
key.content().cmp(lookup.as_bytes())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_utf8_key_comparison() {
let k1 = Utf8Key::new("abc");
let k2 = Utf8Key::new("abd");
let k3 = Utf8Key::new("abc");
assert!(k1 < k2);
assert_eq!(k1, k3);
}
#[test]
fn test_utf8_key_from_str() {
let k1: Utf8Key = "test".into();
let k2 = Utf8Key::from("test");
assert_eq!(k1, k2);
assert_eq!(k1.as_str(), "test");
assert_eq!(k1.as_bytes(), b"test");
}
#[test]
fn test_utf8_key_from_string() {
let s = String::from("hello");
let k: Utf8Key = s.into();
assert_eq!(k.as_str(), "hello");
}
#[test]
fn test_utf8_key_display() {
let k = Utf8Key::new("mykey");
assert_eq!(format!("{k}"), "Utf8Key{mykey}");
}
#[test]
fn test_key_new() {
let bytes = vec![0, 4, b't', b'e', b's', b't', 0, 0]; let key = Key::new(&bytes, 0, 6);
assert_eq!(key.content_length(), 4);
assert_eq!(key.content(), b"test");
assert_eq!(key.content_as_str().unwrap(), "test");
assert_eq!(key.length(), 6);
}
#[test]
fn test_key_from_bytes() {
let bytes = vec![0, 3, b'a', b'b', b'c'];
let key = Key::from_bytes(bytes);
assert_eq!(key.content_length(), 3);
assert_eq!(key.content(), b"abc");
}
#[test]
fn test_key_content_empty() {
let bytes = vec![0];
let key = Key::new(&bytes, 0, 1);
assert_eq!(key.content_length(), 0);
}
#[test]
fn test_key_content_out_of_bounds() {
let bytes = vec![0, 10, b'a', b'b', b'c'];
let key = Key::from_bytes(bytes);
assert_eq!(key.content(), &[] as &[u8]);
}
#[test]
fn test_key_equality() {
let bytes1 = vec![0, 3, b'a', b'b', b'c'];
let bytes2 = vec![0, 3, b'a', b'b', b'c'];
let bytes3 = vec![0, 3, b'x', b'y', b'z'];
let k1 = Key::from_bytes(bytes1);
let k2 = Key::from_bytes(bytes2);
let k3 = Key::from_bytes(bytes3);
assert_eq!(k1, k2);
assert_ne!(k1, k3);
}
#[test]
fn test_key_ordering() {
let k1 = Key::from_bytes(vec![0, 3, b'a', b'b', b'c']);
let k2 = Key::from_bytes(vec![0, 3, b'a', b'b', b'd']);
let k3 = Key::from_bytes(vec![0, 3, b'a', b'b', b'c']);
assert!(k1 < k2);
assert_eq!(k1.cmp(&k3), Ordering::Equal);
}
#[test]
fn test_key_hash() {
use std::collections::HashSet;
let k1 = Key::from_bytes(vec![0, 3, b'a', b'b', b'c']);
let k2 = Key::from_bytes(vec![0, 3, b'a', b'b', b'c']);
let mut set = HashSet::new();
set.insert(k1);
assert!(set.contains(&k2));
}
#[test]
fn test_key_display() {
let k1 = Key::from_bytes(vec![0, 4, b't', b'e', b's', b't']);
assert_eq!(format!("{k1}"), "Key{test}");
let k2 = Key::from_bytes(vec![0, 3, 0xFF, 0xFE, 0xFD]);
assert_eq!(format!("{k2}"), "Key{<binary>}");
}
#[test]
fn test_key_bytes() {
let original = vec![0, 3, b'a', b'b', b'c'];
let key = Key::from_bytes(original.clone());
assert_eq!(key.bytes(), &original);
}
#[test]
fn test_keyvalue_parse() {
let mut bytes = vec![];
bytes.extend_from_slice(&11i32.to_be_bytes()); bytes.extend_from_slice(&5i32.to_be_bytes()); bytes.extend_from_slice(&[0, 4]); bytes.extend_from_slice(b"test"); bytes.extend_from_slice(&[0, 0, 0, 0, 0]); bytes.extend_from_slice(b"value"); bytes.push(0);
let kv = KeyValue::parse(&bytes, 0);
assert_eq!(kv.key().content_as_str().unwrap(), "test");
assert_eq!(kv.value(), b"value");
assert_eq!(kv.key_length(), 11);
assert_eq!(kv.value_length(), 5);
assert_eq!(kv.record_size(), 8 + 11 + 5 + 1); }
#[test]
fn test_keyvalue_display() {
let mut bytes = vec![];
bytes.extend_from_slice(&6i32.to_be_bytes()); bytes.extend_from_slice(&3i32.to_be_bytes()); bytes.extend_from_slice(&[0, 4]); bytes.extend_from_slice(b"test"); bytes.extend_from_slice(b"val"); bytes.push(0);
let kv = KeyValue::parse(&bytes, 0);
assert!(format!("{kv}").contains("test"));
}
#[test]
fn test_compare_keys() {
let key = Key::from_bytes(vec![0, 3, b'a', b'b', b'c']);
let lookup1 = Utf8Key::new("abc");
let lookup2 = Utf8Key::new("abd");
let lookup3 = Utf8Key::new("abb");
assert_eq!(compare_keys(&key, &lookup1), Ordering::Equal);
assert_eq!(compare_keys(&key, &lookup2), Ordering::Less);
assert_eq!(compare_keys(&key, &lookup3), Ordering::Greater);
}
#[test]
fn a_record_at_a_nonzero_offset_reads_itself_and_holds_only_itself() {
fn record(key_content: &[u8], value: &[u8]) -> Vec<u8> {
let mut key = Vec::new();
key.extend_from_slice(&(key_content.len() as i16).to_be_bytes());
key.extend_from_slice(key_content);
let mut out = Vec::new();
out.extend_from_slice(&(key.len() as i32).to_be_bytes());
out.extend_from_slice(&(value.len() as i32).to_be_bytes());
out.extend_from_slice(&key);
out.extend_from_slice(value);
out.push(0); out
}
let first = record(b"aaa", b"value-of-first");
let second = record(b"bbbb", b"second-value");
let mut block = first.clone();
block.extend_from_slice(&second);
let kv0 = KeyValue::parse(&block, 0);
assert_eq!(kv0.key().content(), b"aaa");
assert_eq!(kv0.value(), b"value-of-first");
assert_eq!(kv0.record_size(), first.len());
let kv1 = KeyValue::parse(&block, kv0.record_size());
assert_eq!(
kv1.key().content(),
b"bbbb",
"the second record's key must be read from its own offset"
);
assert_eq!(
kv1.value(),
b"second-value",
"the second record's value must be read from its own offset"
);
assert_eq!(kv1.record_size(), second.len());
assert_eq!(
kv1.key().bytes().len(),
kv1.key_length(),
"a parsed key must hold exactly its own bytes, not the block's"
);
assert!(
kv1.bytes.len() < block.len(),
"a parsed record must hold less than the whole block, got {} of {}",
kv1.bytes.len(),
block.len()
);
assert_eq!(
kv1.bytes.len(),
KEY_VALUE_HEADER_SIZE + kv1.key_length() + kv1.value_length(),
"a parsed record must hold exactly its header, key and value"
);
}
}