use num_traits::{CheckedShl, FromPrimitive, PrimInt, Unsigned};
use thiserror::Error;
pub fn decode<N>(buffer: &[u8]) -> Result<(N, usize), Error>
where
N: PrimInt + Unsigned + FromPrimitive + CheckedShl,
{
let mut num = N::zero();
if buffer.is_empty() {
return Err(Error::EmptyBuffer);
}
let byte_num = (core::mem::size_of::<N>() * 8).div_ceil(7);
for (i, byte) in buffer.iter().take(byte_num).copied().enumerate() {
num = num
| N::from_u8(byte & 0x7F)
.expect("Anything should be able to represent a byte")
.checked_shl(7 * i as u32)
.ok_or(Error::Overflow)?;
if byte & 0x80 == 0 {
return Ok((num, i + 1));
}
}
Err(Error::UnexpectedEof)
}
#[derive(Debug, Error, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Error {
#[error("buffer is empty")]
EmptyBuffer,
#[error("unexpected end of buffer before end of varint")]
UnexpectedEof,
#[error("decoded value does not fit into the target integer type")]
Overflow,
}
impl Error {
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[must_use]
pub fn into_io_error(self) -> std::io::Error {
self.into()
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl From<Error> for std::io::Error {
fn from(error: Error) -> Self {
let kind = match error {
Error::EmptyBuffer | Error::UnexpectedEof => std::io::ErrorKind::UnexpectedEof,
Error::Overflow => std::io::ErrorKind::InvalidData,
};
Self::new(kind, error)
}
}
#[cfg(test)]
mod test {
use crate::decode;
#[test]
fn d1() {
let num: u8 = decode(&[0x01]).unwrap().0;
assert_eq!(num, 0x01_u8);
}
#[test]
fn d7f() {
let num: u8 = decode(&[0x7f]).unwrap().0;
assert_eq!(num, 0x7f_u8);
}
#[test]
fn d80() {
let num: u8 = decode(&[0x80, 0x01]).unwrap().0;
assert_eq!(num, 0x80_u8);
}
#[test]
fn dff() {
let num: u8 = decode(&[0xff, 0x01]).unwrap().0;
assert_eq!(num, 0xff_u8);
}
#[test]
fn d12c() {
let num: u16 = decode(&[0xac, 0x02]).unwrap().0;
assert_eq!(num, 0x12c_u16);
}
#[test]
fn d4000() {
let num: u16 = decode(&[0x80, 0x80, 0x01]).unwrap().0;
assert_eq!(num, 0x4000_u16);
}
}