pub(crate) const MAX_LEN: usize = 10;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg(feature = "decompression")]
pub(crate) enum VarintError {
Truncated,
Overlong,
}
#[cfg(feature = "compression")]
pub(crate) const fn encode(mut value: u64) -> ([u8; MAX_LEN], usize) {
let mut bytes = [0u8; MAX_LEN];
let mut len = 0;
while value >= 0x80 {
bytes[len] = (value as u8 & 0x7f) | 0x80;
value >>= 7;
len += 1;
}
bytes[len] = value as u8;
(bytes, len + 1)
}
#[cfg(feature = "compression")]
pub(crate) fn push(out: &mut alloc::vec::Vec<u8>, value: u64) {
let (bytes, len) = encode(value);
out.extend_from_slice(&bytes[..len]);
}
#[cfg(feature = "decompression")]
pub(crate) fn decode(input: &[u8]) -> Result<(u64, usize), VarintError> {
let mut value = 0u64;
for (index, &byte) in input.iter().take(MAX_LEN).enumerate() {
if index == MAX_LEN - 1 {
if byte & 0x80 != 0 {
return Err(VarintError::Overlong);
}
value |= u64::from(byte & 1) << 63;
return Ok((value, MAX_LEN));
}
value |= u64::from(byte & 0x7f) << (7 * index);
if byte & 0x80 == 0 {
return Ok((value, index + 1));
}
}
Err(VarintError::Truncated)
}
#[cfg(all(test, feature = "compression", feature = "decompression"))]
mod tests {
use super::*;
use alloc::vec::Vec;
#[test]
fn values_round_trip() {
for value in [
0,
1,
127,
128,
300,
16_383,
16_384,
u64::from(u32::MAX),
u64::MAX,
] {
let (bytes, len) = encode(value);
assert_eq!(decode(&bytes[..len]), Ok((value, len)));
let mut out = Vec::new();
push(&mut out, value);
assert_eq!(out, &bytes[..len]);
}
}
#[test]
fn decoding_matches_the_reference_leniency() {
assert_eq!(decode(&[0x80, 0x00]), Ok((0, 2)));
assert_eq!(decode(&[0xad, 0x02, 0xff]), Ok((301, 2)));
let mut ten = [0xffu8; 10];
ten[9] = 0x7f;
assert_eq!(decode(&ten), Ok((u64::MAX, 10)));
}
#[test]
fn malformed_varints_are_rejected() {
assert_eq!(decode(&[]), Err(VarintError::Truncated));
assert_eq!(decode(&[0x80, 0x80]), Err(VarintError::Truncated));
assert_eq!(decode(&[0xff; 10]), Err(VarintError::Overlong));
}
}