use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DecodeError(pub &'static str);
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "decode error: {}", self.0)
}
}
impl std::error::Error for DecodeError {}
pub type DecodeResult<T> = Result<T, DecodeError>;
pub const MAX_PAYLOAD: usize = 64 << 20;
#[inline]
pub fn put_varint(out: &mut Vec<u8>, mut value: u64) {
while value >= 0x80 {
out.push((value as u8) | 0x80);
value >>= 7;
}
out.push(value as u8);
}
#[inline]
pub fn varint_len(mut value: u64) -> usize {
let mut n = 1;
while value >= 0x80 {
value >>= 7;
n += 1;
}
n
}
#[inline]
pub fn put_bytes(out: &mut Vec<u8>, bytes: &[u8]) {
put_varint(out, bytes.len() as u64);
out.extend_from_slice(bytes);
}
#[inline]
pub fn put_u32(out: &mut Vec<u8>, value: u32) {
out.extend_from_slice(&value.to_le_bytes());
}
#[inline]
pub fn put_u64(out: &mut Vec<u8>, value: u64) {
out.extend_from_slice(&value.to_le_bytes());
}
#[derive(Clone, Copy)]
pub struct Reader<'a> {
bytes: &'a [u8],
}
impl<'a> Reader<'a> {
pub fn new(bytes: &'a [u8]) -> Self {
Self { bytes }
}
pub fn remaining(&self) -> usize {
self.bytes.len()
}
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
pub fn rest(&self) -> &'a [u8] {
self.bytes
}
pub fn take(&mut self, n: usize) -> DecodeResult<&'a [u8]> {
if n > self.bytes.len() {
return Err(DecodeError("truncated"));
}
let (head, tail) = self.bytes.split_at(n);
self.bytes = tail;
Ok(head)
}
pub fn u8(&mut self) -> DecodeResult<u8> {
let [b, rest @ ..] = self.bytes else {
return Err(DecodeError("truncated"));
};
self.bytes = rest;
Ok(*b)
}
pub fn u32(&mut self) -> DecodeResult<u32> {
let b = self.take(4)?;
Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
}
pub fn u64(&mut self) -> DecodeResult<u64> {
let b = self.take(8)?;
let mut a = [0u8; 8];
a.copy_from_slice(b);
Ok(u64::from_le_bytes(a))
}
pub fn varint(&mut self) -> DecodeResult<u64> {
let mut value = 0u64;
let mut shift = 0u32;
loop {
let byte = self.u8()?;
if shift == 63 && byte > 1 {
return Err(DecodeError("varint overflow"));
}
value |= u64::from(byte & 0x7f) << shift;
if byte & 0x80 == 0 {
return Ok(value);
}
shift += 7;
if shift > 63 {
return Err(DecodeError("varint overflow"));
}
}
}
pub fn varint_u32(&mut self) -> DecodeResult<u32> {
u32::try_from(self.varint()?).map_err(|_| DecodeError("u32 out of range"))
}
pub fn bytes(&mut self) -> DecodeResult<&'a [u8]> {
let len = self.varint()?;
let len = usize::try_from(len).map_err(|_| DecodeError("length out of range"))?;
if len > MAX_PAYLOAD {
return Err(DecodeError("payload exceeds bound"));
}
self.take(len)
}
pub fn blob(&mut self) -> DecodeResult<&'a [u8]> {
let len = self.varint()?;
let len = usize::try_from(len).map_err(|_| DecodeError("length out of range"))?;
self.take(len)
}
pub fn str(&mut self) -> DecodeResult<&'a str> {
std::str::from_utf8(self.bytes()?).map_err(|_| DecodeError("invalid utf-8"))
}
}
pub fn xxh64(data: &[u8], seed: u64) -> u64 {
const P1: u64 = 0x9E37_79B1_85EB_CA87;
const P2: u64 = 0xC2B2_AE3D_27D4_EB4F;
const P3: u64 = 0x1656_67B1_9E37_79F9;
const P4: u64 = 0x85EB_CA77_C2B2_AE63;
const P5: u64 = 0x27D4_EB2F_1656_67C5;
#[inline(always)]
fn round(acc: u64, input: u64) -> u64 {
acc.wrapping_add(input.wrapping_mul(P2))
.rotate_left(31)
.wrapping_mul(P1)
}
#[inline(always)]
fn merge(acc: u64, val: u64) -> u64 {
(acc ^ round(0, val)).wrapping_mul(P1).wrapping_add(P4)
}
#[inline(always)]
fn u64_at(b: &[u8], i: usize) -> u64 {
let mut a = [0u8; 8];
a.copy_from_slice(&b[i..i + 8]);
u64::from_le_bytes(a)
}
#[inline(always)]
fn u32_at(b: &[u8], i: usize) -> u64 {
u64::from(u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]))
}
let len = data.len();
let mut i = 0;
let mut h = if len >= 32 {
let mut v1 = seed.wrapping_add(P1).wrapping_add(P2);
let mut v2 = seed.wrapping_add(P2);
let mut v3 = seed;
let mut v4 = seed.wrapping_sub(P1);
while i + 32 <= len {
v1 = round(v1, u64_at(data, i));
v2 = round(v2, u64_at(data, i + 8));
v3 = round(v3, u64_at(data, i + 16));
v4 = round(v4, u64_at(data, i + 24));
i += 32;
}
let mut h = v1
.rotate_left(1)
.wrapping_add(v2.rotate_left(7))
.wrapping_add(v3.rotate_left(12))
.wrapping_add(v4.rotate_left(18));
h = merge(h, v1);
h = merge(h, v2);
h = merge(h, v3);
merge(h, v4)
} else {
seed.wrapping_add(P5)
};
h = h.wrapping_add(len as u64);
while i + 8 <= len {
h ^= round(0, u64_at(data, i));
h = h.rotate_left(27).wrapping_mul(P1).wrapping_add(P4);
i += 8;
}
if i + 4 <= len {
h ^= u32_at(data, i).wrapping_mul(P1);
h = h.rotate_left(23).wrapping_mul(P2).wrapping_add(P3);
i += 4;
}
while i < len {
h ^= u64::from(data[i]).wrapping_mul(P5);
h = h.rotate_left(11).wrapping_mul(P1);
i += 1;
}
h ^= h >> 33;
h = h.wrapping_mul(P2);
h ^= h >> 29;
h = h.wrapping_mul(P3);
h ^= h >> 32;
h
}
pub fn checksum64(parts: &[&[u8]]) -> u64 {
parts.iter().fold(0, |seed, part| xxh64(part, seed))
}
pub fn hash_id(id: &str) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for b in id.as_bytes() {
h ^= u64::from(*b);
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
h ^= h >> 33;
h = h.wrapping_mul(0xff51_afd7_ed55_8ccd);
h ^= h >> 33;
h = h.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
h ^ (h >> 33)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn varints_round_trip() {
for v in [0u64, 1, 127, 128, 300, u32::MAX as u64, u64::MAX] {
let mut out = Vec::new();
put_varint(&mut out, v);
assert_eq!(out.len(), varint_len(v));
assert_eq!(Reader::new(&out).varint(), Ok(v));
}
}
#[test]
fn malformed_varints_fail() {
assert!(Reader::new(&[0x80; 11]).varint().is_err());
assert!(Reader::new(&[0x80]).varint().is_err());
assert!(Reader::new(&[]).u8().is_err());
}
#[test]
fn hash_is_stable() {
assert_eq!(hash_id("order-123"), 0x5dd2_c105_c3ea_7bd3);
assert_eq!(hash_id(""), 0xefd0_1f60_ba99_2926);
assert_ne!(hash_id("order-123"), hash_id("order-124"));
}
#[test]
fn checksum_halves_are_independent() {
use std::collections::HashMap;
let mut by_low: HashMap<u32, u64> = HashMap::new();
let mut low_collisions = 0;
for i in 0..200_000u64 {
let sum = checksum64(&[&7u32.to_le_bytes(), &i.to_le_bytes(), &1u32.to_le_bytes()]);
if let Some(other) = by_low.insert(sum as u32, sum) {
low_collisions += 1;
assert_ne!(other, sum, "a 64-bit collision among 200,000 inputs");
}
}
assert!(low_collisions < 64, "{low_collisions}");
}
#[test]
fn checksum_depends_on_every_part_and_its_place() {
let a = checksum64(&[b"ab", b"c"]);
assert_eq!(a, checksum64(&[b"ab", b"c"]));
assert_ne!(a, checksum64(&[b"a", b"bc"]));
assert_ne!(a, checksum64(&[b"c", b"ab"]));
assert_ne!(a, checksum64(&[b"ab", b"d"]));
assert_ne!(a, checksum64(&[b"ab"]));
assert_eq!(checksum64(&[]), 0);
assert_eq!(checksum64(&[b""]), 0xEF46_DB37_51D8_E999);
}
#[test]
fn xxh64_matches_reference_vectors() {
assert_eq!(xxh64(b"", 0), 0xEF46_DB37_51D8_E999);
assert_eq!(xxh64(b"a", 0), 0xD24E_C4F1_A98C_6E5B);
assert_eq!(xxh64(b"abc", 0), 0x44BC_2CF5_AD77_0999);
}
#[test]
fn blobs_may_exceed_the_payload_bound() {
let mut out = Vec::new();
put_bytes(&mut out, &vec![7u8; MAX_PAYLOAD + 1]);
assert!(Reader::new(&out).bytes().is_err());
assert_eq!(Reader::new(&out).blob().unwrap().len(), MAX_PAYLOAD + 1);
let mut short = Vec::new();
put_varint(&mut short, u64::MAX >> 1);
assert!(Reader::new(&short).blob().is_err());
}
}