use crate::error::{corrupt, DbResult};
pub const MAX_LEN: usize = 9;
const MAX_EIGHT_BYTE_VALUE: u64 = (1 << 56) - 1;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Decoded {
pub value: u64,
pub len: usize,
}
pub fn encoded_len(value: u64) -> usize {
if value > MAX_EIGHT_BYTE_VALUE {
return 9;
}
let mut remaining = value;
let mut len: usize = 1;
while remaining > 0x7f {
remaining = remaining.wrapping_shr(7);
len = len.saturating_add(1);
}
len
}
pub fn encode(target: &mut [u8], value: u64) -> DbResult<usize> {
let len = encoded_len(value);
let slot = target
.get_mut(..len)
.ok_or_else(|| corrupt("varint does not fit in the destination buffer"))?;
if len == 9 {
write_nine_byte_form(slot, value);
return Ok(9);
}
let mut remaining = value;
for index in (0..len).rev() {
let Some(byte) = slot.get_mut(index) else {
return Err(corrupt("varint slot shrank while writing"));
};
let payload = (remaining & 0x7f) as u8;
*byte = if index.saturating_add(1) == len {
payload
} else {
payload | 0x80
};
remaining = remaining.wrapping_shr(7);
}
Ok(len)
}
fn write_nine_byte_form(slot: &mut [u8], value: u64) {
let mut remaining = value.wrapping_shr(8);
for index in (0..8).rev() {
if let Some(byte) = slot.get_mut(index) {
*byte = ((remaining & 0x7f) as u8) | 0x80;
}
remaining = remaining.wrapping_shr(7);
}
if let Some(byte) = slot.get_mut(8) {
*byte = (value & 0xff) as u8;
}
}
pub fn decode(source: &[u8]) -> DbResult<Decoded> {
let mut value: u64 = 0;
for index in 0..8 {
let byte = *source
.get(index)
.ok_or_else(|| corrupt("varint ran off the end of the buffer"))?;
value = value.wrapping_shl(7) | u64::from(byte & 0x7f);
if byte & 0x80 == 0 {
return Ok(Decoded {
value,
len: index.saturating_add(1),
});
}
}
let last = *source
.get(8)
.ok_or_else(|| corrupt("nine-byte varint ran off the end of the buffer"))?;
Ok(Decoded {
value: value.wrapping_shl(8) | u64::from(last),
len: 9,
})
}
pub fn encode_i64(target: &mut [u8], value: i64) -> DbResult<usize> {
encode(target, value as u64)
}
pub fn decode_i64(source: &[u8]) -> DbResult<(i64, usize)> {
let decoded = decode(source)?;
Ok((decoded.value as i64, decoded.len))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::PrimaryCode;
use crate::rng::Rng;
fn round_trip(value: u64) -> usize {
let mut buffer = [0u8; MAX_LEN];
let written = encode(&mut buffer, value).expect("nine bytes is always enough");
assert_eq!(
written,
encoded_len(value),
"encoded_len disagrees for {value}"
);
let decoded = decode(&buffer).expect("what we just wrote must decode");
assert_eq!(decoded.value, value);
assert_eq!(decoded.len, written);
written
}
#[test]
fn width_boundaries_use_the_documented_number_of_bytes() {
let cases: [(u64, usize); 11] = [
(0, 1),
(0x7f, 1),
(0x80, 2),
(0x3fff, 2),
(0x4000, 3),
(0x001f_ffff, 3),
(0x0020_0000, 4),
(0x000f_ffff_ffff_ffff, 8),
(MAX_EIGHT_BYTE_VALUE, 8),
(MAX_EIGHT_BYTE_VALUE + 1, 9),
(u64::MAX, 9),
];
for (value, expected) in cases {
assert_eq!(round_trip(value), expected, "wrong width for {value}");
}
}
#[test]
fn the_nine_byte_form_carries_a_full_last_byte() {
let mut buffer = [0u8; MAX_LEN];
encode(&mut buffer, u64::MAX).unwrap();
assert_eq!(buffer, [0xff; 9]);
assert_eq!(
decode(&buffer).unwrap(),
Decoded {
value: u64::MAX,
len: 9
}
);
}
#[test]
fn signed_values_round_trip() {
for value in [0_i64, -1, i64::MIN, i64::MAX, -4_294_967_296] {
let mut buffer = [0u8; MAX_LEN];
let written = encode_i64(&mut buffer, value).unwrap();
let (decoded, len) = decode_i64(&buffer).unwrap();
assert_eq!(decoded, value);
assert_eq!(len, written);
}
}
#[test]
fn truncated_input_is_an_error_at_every_width() {
for value in [0x80_u64, 0x4000, 1 << 40, u64::MAX] {
let mut buffer = [0u8; MAX_LEN];
let written = encode(&mut buffer, value).unwrap();
for prefix in 0..written {
let error = decode(&buffer[..prefix]).expect_err("a truncated varint must fail");
assert_eq!(error.code(), PrimaryCode::Corrupt);
}
}
}
#[test]
fn encoding_refuses_a_buffer_that_is_too_small() {
let mut buffer = [0u8; 2];
assert_eq!(
encode(&mut buffer, 1 << 40).unwrap_err().code(),
PrimaryCode::Corrupt
);
}
#[test]
fn random_values_round_trip_canonically() {
let mut rng = Rng::new(0x1782_0001);
for _ in 0..crate::probe::sample_rounds(200_000) {
let width = rng.below(64) as u32;
let value = rng.next_u64() >> (63 - width.min(63));
let mut first = [0u8; MAX_LEN];
let mut second = [0u8; MAX_LEN];
let a = encode(&mut first, value).unwrap();
let b = encode(&mut second, value).unwrap();
assert_eq!((a, first), (b, second));
assert_eq!(decode(&first).unwrap().value, value);
}
}
#[test]
fn arbitrary_bytes_never_panic() {
let mut rng = Rng::new(0x1782_0002);
let mut buffer = [0u8; 12];
for _ in 0..crate::probe::sample_rounds(200_000) {
rng.fill(&mut buffer);
let len = rng.below(buffer.len() as u64 + 1) as usize;
let _ = decode(&buffer[..len]);
}
}
}