#![no_std]
#![doc(html_root_url = "https://docs.rs/vint64/1.0.0")]
#![warn(missing_docs, rust_2018_idioms, unused_qualifications, unsafe_code)]
#[cfg(feature = "std")]
extern crate std;
mod error;
pub mod signed;
pub use self::error::Error;
use core::{
convert::{TryFrom, TryInto},
fmt::{self, Debug},
};
pub const MAX_BYTES: usize = 9;
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct VInt64 {
length: u8,
bytes: [u8; MAX_BYTES],
}
impl AsRef<[u8]> for VInt64 {
#[inline]
fn as_ref(&self) -> &[u8] {
&self.bytes[..self.length as usize]
}
}
impl Debug for VInt64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut bytes_ref = self.as_ref();
write!(f, "VInt64({})", decode(&mut bytes_ref).unwrap())
}
}
impl From<u64> for VInt64 {
#[inline]
fn from(value: u64) -> VInt64 {
encode(value)
}
}
impl From<i64> for VInt64 {
#[inline]
fn from(value: i64) -> VInt64 {
signed::zigzag::encode(value).into()
}
}
impl TryFrom<&[u8]> for VInt64 {
type Error = Error;
#[inline]
fn try_from(slice: &[u8]) -> Result<Self, Error> {
let mut slice_ref = slice;
decode(&mut slice_ref).map(VInt64::from)
}
}
pub fn encoded_len(value: u64) -> usize {
match value.leading_zeros() {
0..=7 => 9,
8..=14 => 8,
15..=21 => 7,
22..=28 => 6,
29..=35 => 5,
36..=42 => 4,
43..=49 => 3,
50..=56 => 2,
57..=64 => 1,
_ => {
#[allow(unsafe_code)]
unsafe {
core::hint::unreachable_unchecked()
}
}
}
}
#[inline]
pub fn decoded_len(byte: u8) -> usize {
byte.trailing_zeros() as usize + 1
}
#[inline]
pub fn encode(value: u64) -> VInt64 {
let mut bytes = [0u8; MAX_BYTES];
let length = encoded_len(value);
if length == 9 {
bytes[1..].copy_from_slice(&value.to_le_bytes());
} else {
let encoded = (value << 1 | 1) << (length as u64 - 1);
bytes[..8].copy_from_slice(&encoded.to_le_bytes());
}
VInt64 {
bytes,
length: length as u8,
}
}
#[inline]
pub fn decode(input: &mut &[u8]) -> Result<u64, Error> {
let bytes = *input;
let length = decoded_len(*bytes.first().ok_or_else(|| Error::Truncated)?);
if bytes.len() < length {
return Err(Error::Truncated);
}
let result = if length == 9 {
u64::from_le_bytes(bytes[1..9].try_into().unwrap())
} else {
let mut encoded = [0u8; 8];
encoded[..length].copy_from_slice(&bytes[..length]);
u64::from_le_bytes(encoded) >> length
};
if length == 1 || result >= (1 << (7 * (length - 1))) {
*input = &bytes[length..];
Ok(result)
} else {
Err(Error::LeadingZeroes)
}
}
#[cfg(test)]
mod tests {
use super::{decode, encode, signed};
use proptest::{num::u64::ANY, prelude::*};
#[test]
fn encode_zero() {
assert_eq!(encode(0).as_ref(), &[1]);
}
#[test]
fn encode_bit_pattern_examples() {
assert_eq!(encode(0x0f0f).as_ref(), &[0x3e, 0x3c]);
assert_eq!(encode(0x0f0f_f0f0).as_ref(), &[0x08, 0x0f, 0xff, 0xf0]);
assert_eq!(
encode(0x0f0f_f0f0_0f0f).as_ref(),
&[0xc0, 0x87, 0x07, 0x78, 0xf8, 0x87, 0x07]
);
assert_eq!(
encode(0x0f0f_f0f0_0f0f_f0f0).as_ref(),
&[0x00, 0xf0, 0xf0, 0x0f, 0x0f, 0xf0, 0xf0, 0x0f, 0x0f]
);
}
#[test]
fn encode_maxint() {
assert_eq!(
encode(core::u64::MAX).as_ref(),
&[0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
);
}
#[test]
fn encode_signed_values() {
assert_eq!(
signed::encode(0x0f0f_f0f0).as_ref(),
&[0x10, 0x3c, 0xfc, 0xc3, 0x03]
);
assert_eq!(
signed::encode(-0x0f0f_f0f0).as_ref(),
&[0xf0, 0x3b, 0xfc, 0xc3, 0x03]
);
}
#[test]
fn decode_zero() {
let mut slice = [1].as_ref();
assert_eq!(decode(&mut slice).unwrap(), 0);
}
#[test]
fn decode_bit_pattern_examples() {
let mut slice = [0x3e, 0x3c].as_ref();
assert_eq!(decode(&mut slice).unwrap(), 0x0f0f);
assert!(slice.is_empty());
let mut slice = [0x08, 0x0f, 0xff, 0xf0].as_ref();
assert_eq!(decode(&mut slice).unwrap(), 0x0f0f_f0f0);
assert!(slice.is_empty());
let mut slice = [0xc0, 0x87, 0x07, 0x78, 0xf8, 0x87, 0x07].as_ref();
assert_eq!(decode(&mut slice).unwrap(), 0x0f0f_f0f0_0f0f);
assert!(slice.is_empty());
let mut slice = [0x00, 0xf0, 0xf0, 0x0f, 0x0f, 0xf0, 0xf0, 0x0f, 0x0f].as_ref();
assert_eq!(decode(&mut slice).unwrap(), 0x0f0f_f0f0_0f0f_f0f0);
assert!(slice.is_empty());
}
#[test]
fn decode_maxint() {
let mut slice = [0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff].as_ref();
assert_eq!(decode(&mut slice).unwrap(), core::u64::MAX);
assert!(slice.is_empty());
}
#[test]
fn decode_with_trailing_data() {
let mut slice = [0x3e, 0x3c, 0xde, 0xad, 0xbe, 0xef].as_ref();
assert_eq!(decode(&mut slice).unwrap(), 0x0f0f);
assert_eq!(slice, &[0xde, 0xad, 0xbe, 0xef]);
}
#[test]
fn decode_truncated() {
let mut slice = [0].as_ref();
assert!(decode(&mut slice).is_err());
let mut slice = [0x08, 0x0f, 0xff].as_ref();
assert!(decode(&mut slice).is_err());
}
#[test]
fn decode_trailing_zeroes() {
let mut slice = [0x08, 0x00, 0x00, 0x00].as_ref();
assert!(decode(&mut slice).is_err());
}
#[test]
fn decode_signed_values() {
let mut slice = [0x10, 0x3c, 0xfc, 0xc3, 0x03].as_ref();
assert_eq!(signed::decode(&mut slice).unwrap(), 0x0f0f_f0f0);
let mut slice = [0xf0, 0x3b, 0xfc, 0xc3, 0x03].as_ref();
assert_eq!(signed::decode(&mut slice).unwrap(), -0x0f0f_f0f0);
}
proptest! {
#[test]
fn roundtrip(n in ANY) {
let encoded = encode(n);
let decoded = decode(&mut encoded.as_ref()).unwrap();
assert_eq!(n, decoded);
}
}
}