#![allow(dead_code)]
fn write_varint(buf: &mut Vec<u8>, mut value: u64) {
while value >= 0x80 {
buf.push(((value as u8) & 0x7F) | 0x80);
value >>= 7;
}
buf.push(value as u8);
}
fn read_varint(bytes: &[u8], cursor: &mut usize) -> u64 {
let mut result: u64 = 0;
let mut shift: u32 = 0;
loop {
let byte = bytes[*cursor];
*cursor += 1;
result |= u64::from(byte & 0x7F) << shift;
if byte & 0x80 == 0 {
break;
}
shift += 7;
}
result
}
fn shared_prefix_len(a: &[u8], b: &[u8]) -> usize {
a.iter().zip(b.iter()).take_while(|(x, y)| x == y).count()
}
pub(super) fn encode_block_terms(terms: &[&[u8]]) -> Vec<u8> {
if terms.is_empty() {
return Vec::new();
}
let cap = terms.iter().map(|t| t.len() + 2).sum();
let mut out = Vec::with_capacity(cap);
write_varint(&mut out, terms[0].len() as u64);
out.extend_from_slice(terms[0]);
for window in terms.windows(2) {
let prev = window[0];
let curr = window[1];
let shared = shared_prefix_len(prev, curr);
let suffix = &curr[shared..];
write_varint(&mut out, shared as u64);
write_varint(&mut out, suffix.len() as u64);
out.extend_from_slice(suffix);
}
out
}
pub(super) struct FrontCodingDecoder<'a> {
bytes: &'a [u8],
cursor: usize,
remaining: u32,
current: Vec<u8>,
is_first: bool,
}
impl<'a> FrontCodingDecoder<'a> {
pub(super) fn new(bytes: &'a [u8], term_count: u32) -> Self {
FrontCodingDecoder {
bytes,
cursor: 0,
remaining: term_count,
current: Vec::with_capacity(64),
is_first: true,
}
}
#[allow(clippy::should_implement_trait)]
pub(super) fn next(&mut self) -> Option<&[u8]> {
if self.remaining == 0 {
return None;
}
if self.is_first {
let len = read_varint(self.bytes, &mut self.cursor) as usize;
self.current.clear();
self.current
.extend_from_slice(&self.bytes[self.cursor..self.cursor + len]);
self.cursor += len;
self.is_first = false;
} else {
let shared = read_varint(self.bytes, &mut self.cursor) as usize;
let suffix_len = read_varint(self.bytes, &mut self.cursor) as usize;
self.current.truncate(shared);
self.current
.extend_from_slice(&self.bytes[self.cursor..self.cursor + suffix_len]);
self.cursor += suffix_len;
}
self.remaining -= 1;
Some(&self.current)
}
pub(super) fn remaining(&self) -> u32 {
self.remaining
}
}
#[cfg(test)]
mod tests {
use super::*;
fn round_trip(terms: &[&str]) -> Vec<Vec<u8>> {
let term_bytes: Vec<&[u8]> = terms.iter().map(|t| t.as_bytes()).collect();
let encoded = encode_block_terms(&term_bytes);
let mut decoder = FrontCodingDecoder::new(&encoded, terms.len() as u32);
let mut out = Vec::with_capacity(terms.len());
while let Some(term) = decoder.next() {
out.push(term.to_vec());
}
out
}
#[test]
fn varint_round_trip_small() {
for v in [0u64, 1, 0x7F, 0x80, 0xFF, 0x3FFF, 0x4000, u64::MAX] {
let mut buf = Vec::new();
write_varint(&mut buf, v);
let mut cursor = 0;
assert_eq!(read_varint(&buf, &mut cursor), v);
assert_eq!(cursor, buf.len());
}
}
#[test]
fn shared_prefix_len_basic() {
assert_eq!(shared_prefix_len(b"", b""), 0);
assert_eq!(shared_prefix_len(b"abc", b""), 0);
assert_eq!(shared_prefix_len(b"", b"abc"), 0);
assert_eq!(shared_prefix_len(b"abc", b"abd"), 2);
assert_eq!(shared_prefix_len(b"abc", b"abc"), 3);
assert_eq!(shared_prefix_len(b"abc", b"abcd"), 3);
assert_eq!(shared_prefix_len(b"abc", b"xyz"), 0);
}
#[test]
fn empty_terms_returns_empty_bytes() {
let encoded = encode_block_terms(&[]);
assert!(encoded.is_empty());
let mut decoder = FrontCodingDecoder::new(&encoded, 0);
assert_eq!(decoder.remaining(), 0);
assert!(decoder.next().is_none());
}
#[test]
fn single_term_round_trip() {
let recovered = round_trip(&["hello"]);
assert_eq!(recovered, vec![b"hello".to_vec()]);
}
#[test]
fn multi_term_no_shared_prefix() {
let terms = ["aaaa", "bbbb", "cccc"];
let recovered = round_trip(&terms);
assert_eq!(
recovered,
terms
.iter()
.map(|s| s.as_bytes().to_vec())
.collect::<Vec<_>>()
);
}
#[test]
fn multi_term_full_shared_prefix() {
let terms = ["foobar1", "foobar2", "foobar3"];
let recovered = round_trip(&terms);
assert_eq!(
recovered,
terms
.iter()
.map(|s| s.as_bytes().to_vec())
.collect::<Vec<_>>()
);
}
#[test]
fn multi_term_one_char_diff_sequence() {
let terms = ["a", "ab", "abc", "abcd"];
let recovered = round_trip(&terms);
assert_eq!(
recovered,
terms
.iter()
.map(|s| s.as_bytes().to_vec())
.collect::<Vec<_>>()
);
}
#[test]
fn multi_term_utf8_multibyte() {
let terms = ["日", "日本", "日本語"];
let recovered = round_trip(&terms);
assert_eq!(
recovered,
terms
.iter()
.map(|s| s.as_bytes().to_vec())
.collect::<Vec<_>>()
);
}
#[test]
fn long_term_followed_by_shorter_term() {
let terms = ["abcdef", "abcd"];
let recovered = round_trip(&terms);
assert_eq!(
recovered,
terms
.iter()
.map(|s| s.as_bytes().to_vec())
.collect::<Vec<_>>()
);
}
#[test]
fn round_trip_128_random_sorted_terms() {
let mut state: u64 = 0x9E3779B97F4A7C15;
let mut next_u32 = || -> u32 {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
(state >> 32) as u32
};
let mut set = std::collections::BTreeSet::new();
while set.len() < 128 {
let len = 5 + (next_u32() % 6) as usize;
let mut s = String::with_capacity(len);
for _ in 0..len {
s.push((b'a' + (next_u32() as u8 % 26)) as char);
}
set.insert(s);
}
let terms: Vec<String> = set.into_iter().collect();
let term_strs: Vec<&str> = terms.iter().map(|s| s.as_str()).collect();
let recovered = round_trip(&term_strs);
let recovered_strings: Vec<String> = recovered
.into_iter()
.map(|b| String::from_utf8(b).unwrap())
.collect();
assert_eq!(recovered_strings, terms);
}
#[test]
fn next_returns_none_after_exhaustion() {
let term_bytes: Vec<&[u8]> = vec![b"alpha", b"beta"];
let encoded = encode_block_terms(&term_bytes);
let mut decoder = FrontCodingDecoder::new(&encoded, 2);
assert_eq!(decoder.next().unwrap(), b"alpha");
assert_eq!(decoder.next().unwrap(), b"beta");
assert!(decoder.next().is_none());
assert!(decoder.next().is_none());
assert_eq!(decoder.remaining(), 0);
}
#[test]
fn empty_string_term() {
let term_bytes: Vec<&[u8]> = vec![b"", b"abc"];
let encoded = encode_block_terms(&term_bytes);
let mut decoder = FrontCodingDecoder::new(&encoded, 2);
assert_eq!(decoder.next().unwrap(), b"");
assert_eq!(decoder.next().unwrap(), b"abc");
assert!(decoder.next().is_none());
}
}