#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]
use core::fmt;
#[inline]
#[must_use]
pub const fn max_len<const BITS: u32>() -> usize {
let rem = if BITS % 7 == 0 { 0 } else { 1 };
((BITS / 7) + rem) as usize
}
#[inline]
#[must_use]
pub const fn is_last(byte: u8) -> bool {
byte & 0x80 == 0
}
#[macro_export]
macro_rules! encode_uint_arr {
($func:ident, $num_ty:ty, $bits:literal) => {
#[must_use]
pub const fn $func(
mut value: $num_ty,
) -> Option<(
[u8; (($bits / 7) + if $bits % 7 == 0 { 0 } else { 1 }) as usize],
usize,
)> {
const BITS: u32 = $bits;
if <$num_ty>::BITS > BITS && 1 < value >> BITS - 1 {
return None;
}
let mut output = [0; (($bits / 7) + if $bits % 7 == 0 { 0 } else { 1 }) as usize];
let mut index = 0;
loop {
let mut b = (value & 0x7f) as u8;
value >>= 7;
let done = value == 0;
if !done {
b |= 0x80;
}
output[index] = b;
index += 1;
if done {
return Some((output, index));
}
}
}
};
}
encode_uint_arr!(encode_u32, u32, 32);
encode_uint_arr!(encode_u64, u64, 64);
#[macro_export]
macro_rules! encode_fixed_uint_arr {
($func:ident, $num_ty:ty, $bits:literal) => {
#[must_use]
pub const fn $func(
value: $num_ty,
) -> Option<[u8; (($bits / 7) + if $bits % 7 == 0 { 0 } else { 1 }) as usize]> {
const BITS: u32 = $bits;
if <$num_ty>::BITS > BITS && 1 < value >> BITS - 1 {
return None;
}
let mut output = [0; (($bits / 7) + if $bits % 7 == 0 { 0 } else { 1 }) as usize];
let mut index = 0;
let mut shift: u32 = 0;
loop {
let v = value >> shift;
let mut b = (v & 0x7f) as u8;
let done = shift == BITS - (BITS % 7);
if !done {
b |= 0x80;
}
output[index] = b;
index += 1;
shift += 7;
if done {
return Some(output);
}
}
}
};
}
encode_fixed_uint_arr!(encode_fixed_u32, u32, 32);
encode_fixed_uint_arr!(encode_fixed_u64, u64, 64);
#[macro_export]
macro_rules! decode_uint_arr {
($func:ident, $num_ty:ty, $bits:literal) => {
#[must_use]
pub const fn $func(
input: [u8; (($bits / 7) + if $bits % 7 == 0 { 0 } else { 1 }) as usize],
) -> Option<($num_ty, usize)> {
const BITS: u32 = $bits;
if <$num_ty>::BITS < BITS {
return None;
}
let n = input[0];
if n & 0x80 == 0 {
return Some((n as $num_ty, 1));
}
let mut result = (n & 0x7f) as $num_ty;
let mut shift = 7;
let mut pos = 1;
loop {
let n = input[pos];
if shift == BITS - (BITS % 7) && 1 << (BITS % 7) <= n {
return None;
}
if n & 0x80 == 0 {
result |= (n as $num_ty) << shift;
return Some((result, pos + 1));
}
result |= ((n & 0x7f) as $num_ty) << shift;
shift += 7;
pos += 1;
}
}
};
}
decode_uint_arr!(decode_u32, u32, 32);
decode_uint_arr!(decode_u64, u64, 64);
mod private {
pub trait Sealed {}
impl Sealed for u8 {}
impl Sealed for u16 {}
impl Sealed for u32 {}
impl Sealed for u64 {}
impl Sealed for u128 {}
impl Sealed for i8 {}
impl Sealed for i16 {}
impl Sealed for i32 {}
impl Sealed for i64 {}
impl Sealed for i128 {}
}
pub trait UInt: private::Sealed {
const BITS: u32;
}
impl UInt for u8 {
const BITS: u32 = u8::BITS;
}
impl UInt for u16 {
const BITS: u32 = u16::BITS;
}
impl UInt for u32 {
const BITS: u32 = u32::BITS;
}
impl UInt for u64 {
const BITS: u32 = u64::BITS;
}
impl UInt for u128 {
const BITS: u32 = u128::BITS;
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum InnerError {
NeedMoreBytes,
InvalidEncoding,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Error(InnerError);
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
InnerError::NeedMoreBytes => f.write_str("need more bytes"),
InnerError::InvalidEncoding => f.write_str("invalid encoding"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
impl Error {
#[inline]
#[must_use]
pub const fn is_more_bytes_needed(&self) -> bool {
matches!(self.0, InnerError::NeedMoreBytes)
}
#[inline]
#[must_use]
pub const fn is_invalid_encoding(&self) -> bool {
matches!(self.0, InnerError::InvalidEncoding)
}
}
#[allow(clippy::manual_let_else)]
pub fn encode_uint_slice<T, const BITS: u32>(
mut value: T,
output: &mut [u8],
pos: &mut usize,
) -> Option<usize>
where
T: Copy
+ PartialEq
+ core::ops::BitAnd
+ core::ops::Shr<u32>
+ core::ops::ShrAssign<u32>
+ From<u8>
+ UInt,
<T as core::ops::Shr<u32>>::Output: PartialEq<T>,
u8: TryFrom<<T as core::ops::BitAnd<T>>::Output>,
{
if BITS < T::BITS && value >> BITS != T::from(0) {
return None;
}
let mut index = *pos;
loop {
if output.len() <= index {
return None;
}
let mut b = match u8::try_from(value & T::from(0x7f)) {
Ok(b) => b,
Err(_) => unreachable!(),
};
value >>= 7;
let done = value == T::from(0);
if !done {
b |= 0x80;
}
output[index] = b;
index += 1;
if done {
let len = index - *pos;
*pos = index;
return Some(len);
}
}
}
#[allow(clippy::manual_let_else)]
pub fn encode_fixed_uint_slice<T, const BITS: u32>(
mut value: T,
output: &mut [u8],
pos: &mut usize,
) -> Option<usize>
where
T: Copy + core::ops::BitAnd + core::ops::Shr<u32> + core::ops::ShrAssign<u32> + From<u8> + UInt,
<T as core::ops::Shr<u32>>::Output: PartialEq<T>,
u8: TryFrom<<T as core::ops::BitAnd>::Output>,
{
if BITS < T::BITS && value >> BITS != T::from(0) {
return None;
}
if output[*pos..].len() < max_len::<BITS>() {
return None;
}
let mut index = *pos;
for _ in 0..(max_len::<BITS>() - 1) {
let mut b = match u8::try_from(value & T::from(0x7f)) {
Ok(b) => b,
Err(_) => unreachable!(),
};
b |= 0x80;
value >>= 7;
output[index] = b;
index += 1;
}
let b = match u8::try_from(value & T::from(0x7f)) {
Ok(b) => b,
Err(_) => unreachable!(),
};
output[index] = b;
index += 1;
let len = index - *pos;
*pos = index;
Some(len)
}
pub fn decode_uint_slice<T, const BITS: u32>(input: &[u8], pos: &mut usize) -> Result<T, Error>
where
T: core::ops::Shl<u32, Output = T> + core::ops::BitOrAssign + From<u8> + UInt,
{
assert!(BITS <= T::BITS);
if input.len() <= *pos {
return Err(Error(InnerError::NeedMoreBytes));
}
let n = input[*pos];
if is_last(n) {
*pos += 1;
return Ok(T::from(n));
}
let mut result = T::from(n & 0x7f);
let mut shift: u32 = 7;
let mut idx = *pos + 1;
loop {
if input.len() <= idx {
return Err(Error(InnerError::NeedMoreBytes));
}
let n = input[idx];
if shift == BITS - (BITS % 7) && 1 << (BITS % 7) <= n {
return Err(Error(InnerError::InvalidEncoding));
}
if is_last(n) {
result |= T::from(n) << shift;
*pos = idx + 1;
return Ok(result);
}
result |= T::from(n & 0x7f) << shift;
shift += 7;
idx += 1;
}
}
#[macro_export]
macro_rules! encode_sint_arr {
($func:ident, $num_ty:ty, $bits:literal) => {
#[must_use]
pub fn $func(
mut value: $num_ty,
) -> Option<(
[u8; (($bits / 7) + if $bits % 7 == 0 { 0 } else { 1 }) as usize],
usize,
)> {
const BITS: u32 = $bits;
if BITS < <$num_ty>::BITS {
let v: $num_ty = value >> BITS - 1;
if v != 0 && v != -1 {
return None;
}
}
let mut output = [0; (($bits / 7) + if $bits % 7 == 0 { 0 } else { 1 }) as usize];
let mut index = 0;
loop {
let b = (value & 0x7f) as u8;
value >>= 7;
if (value == 0 && b & 0x40 == 0) || (value == -1 && (b & 0x40) != 0) {
output[index] = b;
return Some((output, index + 1));
}
output[index] = b | 0x80;
index += 1;
}
}
};
}
encode_sint_arr!(encode_s32, i32, 32);
encode_sint_arr!(encode_s64, i64, 64);
#[macro_export]
macro_rules! encode_fixed_sint_arr {
($func:ident, $num_ty:ty, $bits:literal) => {
#[must_use]
pub const fn $func(
mut value: $num_ty,
) -> Option<[u8; (($bits / 7) + if $bits % 7 == 0 { 0 } else { 1 }) as usize]> {
const BITS: u32 = $bits;
if BITS < <$num_ty>::BITS {
let v = value >> BITS - 1;
if v != 0 && v != -1 {
return None;
}
}
let mut output = [0; (($bits / 7) + if $bits % 7 == 0 { 0 } else { 1 }) as usize];
let mut index = 0;
let mut extend_negative = false;
loop {
let b = (value & 0x7f) as u8;
value >>= 7;
output[index] = b | 0x80;
index += 1;
if value == 0 && b & 0x40 == 0 {
break;
}
if value == -1 && (b & 0x40) != 0 {
extend_negative = true;
break;
}
}
loop {
if index == output.len() {
output[index - 1] &= 0x7F;
return Some(output);
}
if extend_negative {
output[index] = 0xFF;
} else {
output[index] = 0x80;
}
index += 1;
}
}
};
}
encode_fixed_sint_arr!(encode_fixed_s32, i32, 32);
encode_fixed_sint_arr!(encode_fixed_s64, i64, 64);
#[macro_export]
macro_rules! decode_sint_arr {
($func:ident, $num_ty:ty, $bits:literal) => {
#[must_use]
pub const fn $func(
input: [u8; (($bits / 7) + if $bits % 7 == 0 { 0 } else { 1 }) as usize],
) -> Option<($num_ty, usize)> {
const BITS: u32 = $bits;
if <$num_ty>::BITS < BITS {
return None;
}
let mut result = 0;
let mut shift = 0;
let mut n;
let mut pos = 0;
loop {
n = input[pos];
let more = n & 0x80 != 0;
if shift == BITS - (BITS % 7) {
#[allow(clippy::cast_sign_loss)]
let mask = ((-1i8 << ((BITS % 7).saturating_sub(1))) & 0x7f) as u8;
if more || (n & mask != 0 && n < mask) {
return None;
}
}
result |= ((n & 0x7f) as $num_ty) << shift;
shift += 7;
pos += 1;
if !more {
break;
}
}
if shift < <$num_ty>::BITS && n & 0x40 != 0 {
result |= -1 << shift;
}
Some((result, pos))
}
};
}
decode_sint_arr!(decode_s32, i32, 32);
decode_sint_arr!(decode_s64, i64, 64);
pub trait SInt: private::Sealed {
const BITS: u32;
}
impl SInt for i8 {
const BITS: u32 = i8::BITS;
}
impl SInt for i16 {
const BITS: u32 = i16::BITS;
}
impl SInt for i32 {
const BITS: u32 = i32::BITS;
}
impl SInt for i64 {
const BITS: u32 = i64::BITS;
}
impl SInt for i128 {
const BITS: u32 = i128::BITS;
}
#[allow(clippy::manual_let_else)]
pub fn encode_sint_slice<T, const BITS: u32>(
mut value: T,
output: &mut [u8],
pos: &mut usize,
) -> Option<usize>
where
T: Copy
+ PartialEq
+ core::ops::BitAnd
+ core::ops::Shr<u32>
+ core::ops::ShrAssign<u32>
+ From<i8>
+ SInt,
<T as core::ops::Shr<u32>>::Output: PartialEq<T>,
u8: TryFrom<<T as core::ops::BitAnd<T>>::Output>,
{
if BITS < T::BITS {
let v = value >> BITS;
if v != T::from(0) && v != T::from(-1) {
return None;
}
}
let mut index = *pos;
loop {
if output.len() <= index {
return None;
}
let b = match u8::try_from(value & T::from(0x7f)) {
Ok(b) => b,
Err(_) => unreachable!(),
};
value >>= 7;
if (value == T::from(0) && b & 0x40 == 0) || (value == T::from(-1) && (b & 0x40) != 0) {
output[index] = b;
index += 1;
let len = index - *pos;
*pos = index;
return Some(len);
}
output[index] = b | 0x80;
index += 1;
}
}
#[allow(clippy::manual_let_else)]
pub fn encode_fixed_sint_slice<T, const BITS: u32>(
mut value: T,
output: &mut [u8],
pos: &mut usize,
) -> Option<usize>
where
T: Copy
+ PartialEq
+ core::ops::BitAnd
+ core::ops::Shr<u32>
+ core::ops::ShrAssign<u32>
+ From<i8>
+ SInt,
<T as core::ops::Shr<u32>>::Output: PartialEq<T>,
u8: TryFrom<<T as core::ops::BitAnd>::Output>,
{
if BITS < T::BITS {
let v = value >> BITS;
if v != T::from(0) && v != T::from(-1) {
return None;
}
}
if output[*pos..].len() < max_len::<BITS>() {
return None;
}
let mut index = *pos;
let mut extend_negative = false;
loop {
let b = match u8::try_from(value & T::from(0x7f)) {
Ok(b) => b,
Err(_) => unreachable!(),
};
value >>= 7;
output[index] = b | 0x80;
index += 1;
if value == T::from(0) && b & 0x40 == 0 {
break;
}
if value == T::from(-1) && (b & 0x40) != 0 {
extend_negative = true;
break;
}
}
loop {
if index == *pos + max_len::<BITS>() {
output[index - 1] &= 0x7F;
let len = index - *pos;
*pos = index;
return Some(len);
}
if extend_negative {
output[index] = 0xFF;
} else {
output[index] = 0x80;
}
index += 1;
}
}
pub fn decode_sint_slice<T, const BITS: u32>(input: &[u8], pos: &mut usize) -> Result<T, Error>
where
T: core::ops::Shl<u32, Output = T> + core::ops::BitOrAssign + From<i8> + From<u8> + SInt,
{
assert!(BITS <= T::BITS);
let mut result = T::from(0i8);
let mut shift = 0;
let mut n;
let mut idx = *pos;
loop {
if input.len() <= idx {
return Err(Error(InnerError::NeedMoreBytes));
}
n = input[idx];
let more = n & 0x80 != 0;
if shift == BITS - (BITS % 7) {
#[allow(clippy::cast_sign_loss)]
let mask = ((-1i8 << ((BITS % 7).saturating_sub(1))) & 0x7f) as u8;
if more || (n & mask != 0 && n < mask) {
return Err(Error(InnerError::InvalidEncoding));
}
}
result |= T::from(n & 0x7f) << shift;
shift += 7;
idx += 1;
if !more {
break;
}
}
if shift < T::BITS && n & 0x40 != 0 {
result |= T::from(-1i8) << shift;
}
*pos = idx;
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_u8() {
let mut buffer = [0; 4];
let mut pos = 1;
let written = encode_fixed_uint_slice::<_, 8>(u8::MAX, &mut buffer, &mut pos);
assert_eq!(3, pos);
assert_eq!([0x00, 0xFF, 0x01, 0x00], buffer);
assert_eq!(Some(2), written);
}
#[test]
fn test_encode_u32() {
let mut buffer = [0; 6];
let mut pos = 1;
let written = encode_fixed_uint_slice::<_, 32>(u32::MAX, &mut buffer, &mut pos);
assert_eq!(6, pos);
assert_eq!([0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F], buffer);
assert_eq!(Some(5), written);
}
#[test]
fn test_encode_u64_as_33_bits_2() {
let mut buffer = [0; 6];
let mut pos = 1;
let written = encode_fixed_uint_slice::<_, 33>(2u64.pow(33) - 1, &mut buffer, &mut pos);
let mut pos = 1;
let value = decode_uint_slice::<u64, 33>(&buffer, &mut pos).unwrap();
assert_eq!(8_589_934_592 - 1, value);
assert_eq!(6, pos);
assert_eq!([0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F], buffer);
assert_eq!(Some(5), written);
}
#[test]
fn test_encode_u64_as_33_bits_with_too_large_value() {
let mut buffer = [0; 6];
let mut pos = 1;
let written = encode_fixed_uint_slice::<_, 33>(2u64.pow(34) - 1, &mut buffer, &mut pos);
assert_eq!(1, pos);
assert_eq!([0x00, 0x00, 0x00, 0x00, 0x00, 0x00], buffer);
assert_eq!(None, written);
}
#[test]
fn test_encode_u64() {
let mut buffer = [0; 20];
let mut pos = 1;
let written = encode_fixed_uint_slice::<_, 64>(u64::MAX, &mut buffer, &mut pos);
assert_eq!(11, pos);
assert_eq!(Some(10), written);
}
#[test]
fn test_decode_u32() {
let input = [0xff, 0xff, 0xff, 0xff, 0x0f];
let result = decode_u32(input);
assert_eq!(result, Some((u32::MAX, 5)));
let input = [0x00, 0x00, 0x00, 0x00, 0x00];
let result = decode_u32(input);
assert_eq!(result, Some((u32::MIN, 1)));
let input = [0x80, 0x80, 0x80, 0x80, 0x00];
let result = decode_u32(input);
assert_eq!(result, Some((u32::MIN, 5)));
}
#[test]
fn test_decode_u32_errors() {
let input = [0xff, 0xff, 0xff, 0xff, 0x8f];
let result = decode_u32(input);
assert_eq!(result, None);
let input = [0xff, 0xff, 0xff, 0xff, 0x1f];
let result = decode_u32(input);
assert_eq!(result, None);
}
#[test]
fn test_decode_u64() {
let input = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01];
let result = decode_u64(input);
assert_eq!(result, Some((u64::MAX, 10)));
let input = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let result = decode_u64(input);
assert_eq!(result, Some((u64::MIN, 1)));
let input = [0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00];
let result = decode_u64(input);
assert_eq!(result, Some((u64::MIN, 10)));
}
#[test]
fn test_decode_u64_errors() {
let input = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81];
let result = decode_u64(input);
assert_eq!(result, None);
let input = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02];
let result = decode_u64(input);
assert_eq!(result, None);
}
#[test]
fn test_decode_s32() {
let input = [0xff, 0xff, 0xff, 0xff, 0x07];
let result = decode_s32(input);
assert_eq!(result, Some((i32::MAX, 5)));
let input = [0x80, 0x80, 0x80, 0x80, 0x78];
let result = decode_s32(input);
assert_eq!(result, Some((i32::MIN, 5)));
let input = [0x00, 0x00, 0x00, 0x00, 0x00];
let result = decode_s32(input);
assert_eq!(result, Some((0, 1)));
let input = [0x80, 0x80, 0x80, 0x80, 0x00];
let result = decode_s32(input);
assert_eq!(result, Some((0, 5)));
let input = [0x40, 0x00, 0x00, 0x00, 0x00];
let result = decode_s32(input);
assert_eq!(result, Some((-64, 1)));
let input = [0xc0, 0x7f, 0x00, 0x00, 0x00];
let result = decode_s32(input);
assert_eq!(result, Some((-64, 2)));
}
#[test]
fn test_decode_s32_errors() {
let input = [0x80, 0x80, 0x80, 0x80, 0x80];
let result = decode_s32(input);
assert_eq!(result, None);
let input = [0x80, 0x80, 0x80, 0x80, 0x08];
let result = decode_s32(input);
assert_eq!(result, None);
let input = [0x80, 0x80, 0x80, 0x80, 0x38];
let result = decode_s32(input);
assert_eq!(result, None);
}
#[test]
fn test_decode_s33() {
decode_sint_arr!(decode_s33, i64, 33);
let input = [0xff, 0xff, 0xff, 0xff, 0x0f];
let result = decode_s33(input);
assert_eq!(result, Some((i64::from(u32::MAX), 5)));
let input = [0x80, 0x80, 0x80, 0x80, 0x70];
let result = decode_s33(input);
assert_eq!(result, Some((i64::from(i32::MIN) * 2, 5)));
let input = [0x00, 0x00, 0x00, 0x00, 0x00];
let result = decode_s33(input);
assert_eq!(result, Some((0, 1)));
let input = [0x80, 0x80, 0x80, 0x80, 0x00];
let result = decode_s33(input);
assert_eq!(result, Some((0, 5)));
let input = [0x40, 0x00, 0x00, 0x00, 0x00];
let result = decode_s33(input);
assert_eq!(result, Some((-64, 1)));
let input = [0xc0, 0x7f, 0x00, 0x00, 0x00];
let result = decode_s33(input);
assert_eq!(result, Some((-64, 2)));
}
#[test]
fn test_decode_s33_errors() {
decode_sint_arr!(decode_s33, i64, 33);
let input = [0x80, 0x80, 0x80, 0x80, 0x80];
let result = decode_s33(input);
assert_eq!(result, None);
let input = [0x80, 0x80, 0x80, 0x80, 0x10];
let result = decode_s33(input);
assert_eq!(result, None);
let input = [0x80, 0x80, 0x80, 0x80, 0x30];
let result = decode_s33(input);
assert_eq!(result, None);
}
#[test]
fn test_decode_s64() {
let input = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00];
let result = decode_s64(input);
assert_eq!(result, Some((i64::MAX, 10)));
let input = [0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f];
let result = decode_s64(input);
assert_eq!(result, Some((i64::MIN, 10)));
let input = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let result = decode_s64(input);
assert_eq!(result, Some((0, 1)));
let input = [0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00];
let result = decode_s64(input);
assert_eq!(result, Some((0, 10)));
}
#[test]
fn test_decode_s64_errors() {
let input = [0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80];
let result = decode_s64(input);
assert_eq!(result, None);
let input = [0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x08];
let result = decode_s64(input);
assert_eq!(result, None);
let input = [0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x28];
let result = decode_s64(input);
assert_eq!(result, None);
}
}