#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone, Copy)]
#[non_exhaustive]
pub enum VarIntError {
#[error("encoded integer overflows usize")]
Overflow,
#[error("unexpected end of input")]
UnexpectedEnd,
}
pub(crate) fn decode(input: &[u8], prefix_size: u8) -> Result<(usize, &[u8]), VarIntError> {
debug_assert!((1..=8).contains(&prefix_size));
let [first, rest @ ..] = input else {
return Err(VarIntError::UnexpectedEnd);
};
let prefix_mask = u8::MAX >> (8 - prefix_size);
let mut value = usize::from(first & prefix_mask);
if value < usize::from(prefix_mask) {
return Ok((value, rest));
}
let mut shift = 0_u32;
for (i, &byte) in rest.iter().enumerate() {
let payload = usize::from(byte & 0x7F);
let increment = payload.checked_shl(shift).ok_or(VarIntError::Overflow)?;
value = value.checked_add(increment).ok_or(VarIntError::Overflow)?;
shift += 7;
if byte & 0x80 == 0 {
return Ok((value, &rest[i + 1..]));
}
}
Err(VarIntError::UnexpectedEnd)
}
#[allow(clippy::cast_possible_truncation)] pub(crate) fn encode(value: usize, prefix_size: u8) -> Vec<u8> {
debug_assert!((1..=8).contains(&prefix_size));
let prefix_max = u8::MAX >> (8 - prefix_size);
if value < usize::from(prefix_max) {
return vec![value as u8];
}
let mut output = vec![prefix_max];
let mut remaining = value - usize::from(prefix_max);
while remaining >= 128 {
output.push(remaining as u8 | 0x80);
remaining >>= 7;
}
output.push(remaining as u8);
output
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_10_prefix_5() {
let (value, rest) = decode(&[0b000_01010], 5).unwrap();
assert_eq!(value, 10);
assert!(rest.is_empty());
}
#[test]
fn decode_1337_prefix_5() {
let (value, rest) = decode(&[0b000_11111, 0b10011010, 0b00001010], 5).unwrap();
assert_eq!(value, 1337);
assert!(rest.is_empty());
}
#[test]
fn decode_42_prefix_8() {
let (value, rest) = decode(&[42], 8).unwrap();
assert_eq!(value, 42);
assert!(rest.is_empty());
}
#[test]
fn decode_ignores_high_bits() {
let (value, rest) = decode(&[0b111_01010], 5).unwrap();
assert_eq!(value, 10);
assert!(rest.is_empty());
}
#[test]
fn decode_returns_remainder() {
let (value, rest) = decode(&[10, 0xAA, 0xBB], 8).unwrap();
assert_eq!(value, 10);
assert_eq!(rest, &[0xAA, 0xBB]);
}
#[test]
fn decode_empty_input() {
assert_eq!(decode(&[], 5), Err(VarIntError::UnexpectedEnd));
}
#[test]
fn decode_truncated_continuation() {
assert_eq!(decode(&[0b000_11111], 5), Err(VarIntError::UnexpectedEnd));
}
#[test]
fn encode_10_prefix_5() {
assert_eq!(encode(10, 5), vec![0b0_1010]);
}
#[test]
fn encode_1337_prefix_5() {
assert_eq!(encode(1337, 5), vec![0b1_1111, 0b1001_1010, 0b0000_1010]);
}
#[test]
fn encode_42_prefix_8() {
assert_eq!(encode(42, 8), vec![42]);
}
#[test]
fn roundtrip_small() {
for n in [1, 5, 7, 8] {
for value in 0..300 {
let encoded = encode(value, n);
let (decoded, rest) = decode(&encoded, n).unwrap();
assert_eq!(decoded, value, "prefix_size={n}, value={value}");
assert!(rest.is_empty());
}
}
}
#[test]
fn roundtrip_large() {
for &value in &[
0,
1,
126,
127,
128,
255,
256,
16383,
16384,
1_000_000,
usize::MAX >> 1,
] {
let encoded = encode(value, 5);
let (decoded, rest) = decode(&encoded, 5).unwrap();
assert_eq!(decoded, value);
assert!(rest.is_empty());
}
}
}