use rudb_common::{Error, Result};
const SEGMENT: usize = 256 * 1024;
const MIN_MATCH: usize = 4;
const HASH_BITS: u32 = 16;
const HASH_LEN: usize = 8;
const MAX_TRIES: usize = 32;
pub(crate) struct Tokens<'a> {
pub literals: Vec<&'a [u8]>,
pub lengths: Vec<i64>,
pub offsets: Vec<i64>,
}
#[derive(Default)]
struct Raw {
runs: Vec<(usize, usize)>,
lengths: Vec<i64>,
offsets: Vec<i64>,
}
pub(crate) fn tokens_of(input: &[u8]) -> Tokens<'_> {
let mut raw = Raw::default();
let mut head = vec![u32::MAX; 1 << HASH_BITS];
let span = SEGMENT.min(input.len()).max(1);
let mut prev = vec![u32::MAX; span];
let mut start = 0;
while start < input.len() {
let end = (start + SEGMENT).min(input.len());
head.fill(u32::MAX);
matches_in(input, start, end, &mut head, &mut prev, &mut raw);
start = end;
}
Tokens {
literals: raw.runs.iter().map(|(from, to)| &input[*from..*to]).collect(),
lengths: raw.lengths,
offsets: raw.offsets,
}
}
fn matches_in(
input: &[u8],
start: usize,
end: usize,
head: &mut [u32],
prev: &mut [u32],
raw: &mut Raw,
) {
let mut literal_start = start;
let mut at = start;
while at < end {
if at + HASH_LEN > end {
break;
}
let key = hash(&input[at..at + HASH_LEN]);
let found = longest(input, at, end, head[key], prev, start);
insert(input, at, end, head, prev, start);
match found {
Some((length, offset)) => {
push(raw, (literal_start, at), length, offset);
for step in 1..length {
insert(input, at + step, end, head, prev, start);
}
at += length;
literal_start = at;
}
None => at += 1,
}
}
if literal_start < end {
push(raw, (literal_start, end), 0, 0);
}
}
fn longest(
input: &[u8],
at: usize,
end: usize,
mut candidate: u32,
prev: &[u32],
start: usize,
) -> Option<(usize, usize)> {
let mut best: Option<(usize, usize)> = None;
let mut tries = 0;
while candidate != u32::MAX && tries < MAX_TRIES {
let position = start + candidate as usize;
if position >= at {
break;
}
if let Some((had, _)) = best {
if at + had >= end {
break;
}
if input[position + had] != input[at + had] {
candidate = prev[candidate as usize];
tries += 1;
continue;
}
}
let length = shared(&input[position..end], &input[at..end]);
if length >= MIN_MATCH && best.is_none_or(|(had, _)| length > had) {
best = Some((length, at - position));
}
candidate = prev[candidate as usize];
tries += 1;
}
best
}
fn insert(input: &[u8], at: usize, end: usize, head: &mut [u32], prev: &mut [u32], start: usize) {
if at + HASH_LEN > end {
return;
}
let key = hash(&input[at..at + HASH_LEN]);
let slot = at - start;
prev[slot] = head[key];
head[key] = slot as u32;
}
fn push(raw: &mut Raw, run: (usize, usize), length: usize, offset: usize) {
raw.runs.push(run);
raw.lengths.push(length as i64);
raw.offsets.push(offset as i64);
}
pub(crate) fn rebuild(
literals: &[Vec<u8>],
lengths: &[i64],
offsets: &[i64],
total: usize,
) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(total);
replay(
literals.len(),
|index, into: &mut Vec<u8>| {
into.extend_from_slice(&literals[index]);
Ok(())
},
lengths,
offsets,
&mut out,
)?;
Ok(out)
}
pub(crate) fn rebuild_into(
literals: &crate::string::Flat,
lengths: &[i64],
offsets: &[i64],
out: &mut Vec<u8>,
) -> Result<()> {
replay(
literals.len(),
|index, into: &mut Vec<u8>| {
into.extend_from_slice(literals.get(index).expect("in range"));
Ok(())
},
lengths,
offsets,
out,
)
}
pub(crate) fn replay(
runs: usize,
mut run: impl FnMut(usize, &mut Vec<u8>) -> Result<()>,
lengths: &[i64],
offsets: &[i64],
out: &mut Vec<u8>,
) -> Result<()> {
if runs != lengths.len() || lengths.len() != offsets.len() {
return Err(Error::internal(format!(
"a matched chunk has {runs} literal runs, {} lengths and {} offsets",
lengths.len(),
offsets.len()
)));
}
let base = out.len();
for index in 0..runs {
run(index, out)?;
let length = usize::try_from(lengths[index])
.map_err(|_| Error::internal("a negative copy length"))?;
if length == 0 {
continue;
}
let offset = usize::try_from(offsets[index])
.map_err(|_| Error::internal("a negative copy offset"))?;
if offset == 0 || offset > out.len() - base {
return Err(Error::internal(format!(
"a copy reaches {offset} bytes back into {} bytes of output",
out.len() - base
)));
}
let from = out.len() - offset;
if offset >= length {
out.extend_from_within(from..from + length);
} else {
out.reserve(length);
for step in 0..length {
let byte = out[from + step];
out.push(byte);
}
}
}
Ok(())
}
fn hash(bytes: &[u8]) -> usize {
let word = u64::from_le_bytes(bytes[..HASH_LEN].try_into().expect("eight bytes to hash"));
(word.wrapping_mul(0x9E37_79B9_7F4A_7C15) >> (64 - HASH_BITS)) as usize
}
fn shared(a: &[u8], b: &[u8]) -> usize {
let cap = a.len().min(b.len());
let mut n = 0;
for (left, right) in a[..cap].chunks_exact(8).zip(b[..cap].chunks_exact(8)) {
let left = u64::from_le_bytes(left.try_into().expect("chunks_exact(8) gives eight bytes"));
let right =
u64::from_le_bytes(right.try_into().expect("chunks_exact(8) gives eight bytes"));
let differ = left ^ right;
if differ != 0 {
return n + (differ.trailing_zeros() / 8) as usize;
}
n += 8;
}
while n < cap && a[n] == b[n] {
n += 1;
}
n
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[ignore = "a measurement over real text, run by hand in release with LZ_DATA set"]
fn measure_on_real_text() {
use crate::string::{Kind, front_code, size_as};
use std::time::{Duration, Instant};
let Ok(dir) = std::env::var("LZ_DATA") else { return };
for name in ["URL", "Title", "Referer"] {
let text = std::fs::read(format!("{dir}/{name}.txt")).expect("a data file");
let values: Vec<&[u8]> =
text.split(|byte| *byte == b'\n').filter(|value| !value.is_empty()).collect();
let (mut raw, mut lz, mut front) = (0, 0, 0);
let mut spent = Duration::ZERO;
for block in values.chunks(1024) {
let joined = block.concat();
let suffixes = front_code(block).1.concat();
raw += joined.len();
let start = Instant::now();
std::hint::black_box(tokens_of(&joined));
std::hint::black_box(tokens_of(&suffixes));
spent += start.elapsed();
lz += size_as(Kind::Lz, block, 0).expect("encodes").expect("applies");
front += size_as(Kind::Front, block, 0).expect("encodes").expect("applies");
}
println!(
"{name}: {raw} bytes, matched in {:.1} ms, LZ {lz} ({:.3}x), FRONT {front} ({:.3}x)",
spent.as_secs_f64() * 1e3,
raw as f64 / lz as f64,
raw as f64 / front as f64,
);
}
}
fn round_trip(input: &[u8]) {
let tokens = tokens_of(input);
let owned: Vec<Vec<u8>> = tokens.literals.iter().map(|run| run.to_vec()).collect();
let back = rebuild(&owned, &tokens.lengths, &tokens.offsets, input.len()).unwrap();
assert_eq!(back, input, "{} tokens", tokens.lengths.len());
}
#[test]
fn nothing_round_trips() {
round_trip(b"");
}
#[test]
fn something_with_no_repeats_round_trips() {
round_trip(b"abcdefghijklmnop");
}
#[test]
fn a_repeat_becomes_a_copy() {
let input = b"the same sentence twice, the same sentence twice";
let tokens = tokens_of(input);
assert!(tokens.lengths.iter().any(|length| *length >= MIN_MATCH as i64), "no copy emitted");
round_trip(input);
}
#[test]
fn a_run_of_one_byte_is_one_overlapping_copy() {
let input = vec![b'x'; 4096];
round_trip(&input);
let tokens = tokens_of(&input);
assert!(tokens.lengths.len() < 8, "{} tokens for one repeated byte", tokens.lengths.len());
}
#[test]
fn a_copy_that_overlaps_by_part_of_itself_round_trips() {
let input: Vec<u8> = (0..8192).map(|index| b"abc"[index % 3]).collect();
round_trip(&input);
let tokens = tokens_of(&input);
assert!(
tokens
.offsets
.iter()
.zip(&tokens.lengths)
.any(|(offset, length)| *offset > 1 && *offset < *length),
"no partly overlapping copy emitted"
);
}
#[test]
fn rebuilding_into_a_buffer_cannot_reach_what_was_already_in_it() {
let input = b"the same sentence twice, the same sentence twice";
let tokens = tokens_of(input);
let packed =
crate::string::encode_only(crate::string::Kind::Plain, &tokens.literals).unwrap();
let literals = crate::string::decode_flat(&packed.expect("plain applies")).unwrap();
let mut out = b"something that was here first".to_vec();
let base = out.len();
rebuild_into(&literals, &tokens.lengths, &tokens.offsets, &mut out).unwrap();
assert_eq!(&out[..base], b"something that was here first");
assert_eq!(&out[base..], input);
let mut offsets = tokens.offsets.clone();
let copy = offsets.iter().position(|offset| *offset > 0).expect("a copy");
offsets[copy] += base as i64;
let mut out = vec![0; base];
let error = rebuild_into(&literals, &tokens.lengths, &offsets, &mut out)
.expect_err("a copy reaching before the base");
assert!(error.message().starts_with("a copy reaches"), "{}", error.message());
}
#[test]
fn something_longer_than_a_segment_round_trips() {
let mut input = Vec::new();
while input.len() < SEGMENT * 2 + 1234 {
input.extend_from_slice(b"http://example.com/some/path?query=value&more=stuff ");
}
round_trip(&input);
}
#[test]
fn the_word_compare_finds_the_same_copies_as_a_byte_compare() {
fn by_bytes(input: &[u8]) -> (Vec<(usize, usize)>, Vec<i64>, Vec<i64>) {
let mut raw = Raw::default();
let mut head = vec![u32::MAX; 1 << HASH_BITS];
let mut prev = vec![u32::MAX; SEGMENT.min(input.len()).max(1)];
let mut start = 0;
while start < input.len() {
let end = (start + SEGMENT).min(input.len());
head.fill(u32::MAX);
let mut literal_start = start;
let mut at = start;
while at + HASH_LEN <= end {
let mut candidate = head[hash(&input[at..at + HASH_LEN])];
let mut found: Option<(usize, usize)> = None;
let mut tries = 0;
while candidate != u32::MAX && tries < MAX_TRIES {
let position = start + candidate as usize;
if position >= at {
break;
}
let mut length = 0;
while at + length < end && input[position + length] == input[at + length] {
length += 1;
}
if length >= MIN_MATCH && found.is_none_or(|(had, _)| length > had) {
found = Some((length, at - position));
}
candidate = prev[candidate as usize];
tries += 1;
}
insert(input, at, end, &mut head, &mut prev, start);
match found {
Some((length, offset)) => {
push(&mut raw, (literal_start, at), length, offset);
for step in 1..length {
insert(input, at + step, end, &mut head, &mut prev, start);
}
at += length;
literal_start = at;
}
None => at += 1,
}
}
if literal_start < end {
push(&mut raw, (literal_start, end), 0, 0);
}
start = end;
}
(raw.runs, raw.lengths, raw.offsets)
}
let mut urls = Vec::new();
for n in 0..9000 {
urls.extend_from_slice(
format!("http://example.com/a/b/{}/{n}?q={}\n", n % 37, n * 7).as_bytes(),
);
}
let mut noise = Vec::new();
let mut state = 0x2545_f491_4f6c_dd1d_u64;
for _ in 0..300_000 {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
noise.push(b"abcab"[(state % 5) as usize]);
}
let inputs: [&[u8]; 6] =
[b"", b"abcabcabcabcabcabcabcx", &[b'z'; 5000], &urls, &noise, &urls[..SEGMENT + 17]];
for input in inputs {
let tokens = tokens_of(input);
let (runs, lengths, offsets) = by_bytes(input);
let literals: Vec<&[u8]> = runs.iter().map(|(from, to)| &input[*from..*to]).collect();
assert_eq!(tokens.literals, literals);
assert_eq!(tokens.lengths, lengths);
assert_eq!(tokens.offsets, offsets);
}
}
#[test]
fn urls_compress() {
let mut input = Vec::new();
for n in 0..4000 {
input.extend_from_slice(format!("http://example.com/page/{n}?ref=search\n").as_bytes());
}
let tokens = tokens_of(&input);
let literal_bytes: usize = tokens.literals.iter().map(|run| run.len()).sum();
assert!(literal_bytes * 4 < input.len(), "{literal_bytes} literal of {}", input.len());
round_trip(&input);
}
#[test]
fn a_copy_that_reaches_too_far_is_refused() {
let literals = vec![b"ab".to_vec()];
assert!(rebuild(&literals, &[4], &[99], 6).is_err());
}
#[test]
fn streams_of_different_lengths_are_refused() {
let literals = vec![b"ab".to_vec()];
assert!(rebuild(&literals, &[0, 0], &[0, 0], 2).is_err());
}
}