#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_docs)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{vec::Vec, string::String};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum EncConfig {
EncodeLower,
EncodeUpper,
}
pub use EncConfig::*;
#[inline]
fn encoded_size(source_len: usize) -> usize {
const USIZE_TOP_BIT: usize = 1usize << (core::mem::size_of::<usize>() * 8 - 1);
if (source_len & USIZE_TOP_BIT) != 0 {
usize_overflow(source_len)
}
source_len << 1
}
#[inline]
fn encode_slice_raw(src: &[u8], cfg: EncConfig, dst: &mut [u8]) {
let lut = if cfg == EncodeLower { HEX_LOWER } else { HEX_UPPER };
debug_assert!(dst.len() == encoded_size(src.len()));
dst.chunks_exact_mut(2).zip(src.iter().copied()).for_each(|(d, sb)| {
d[0] = lut[(sb >> 4) as usize];
d[1] = lut[(sb & 0xf) as usize];
})
}
#[cfg(feature = "alloc")]
#[inline]
fn encode_to_string(bytes: &[u8], cfg: EncConfig) -> String {
let size = encoded_size(bytes.len());
let mut buf: Vec<u8> = Vec::with_capacity(size);
unsafe { buf.set_len(size); }
encode_slice_raw(bytes, cfg, &mut buf);
debug_assert!(core::str::from_utf8(&buf).is_ok());
unsafe { String::from_utf8_unchecked(buf) }
}
#[cfg(feature = "alloc")]
#[inline]
unsafe fn grow_vec_uninitialized(v: &mut Vec<u8>, grow_by: usize) -> usize {
v.reserve(grow_by);
let initial_len = v.len();
let new_len = initial_len + grow_by;
debug_assert!(new_len <= v.capacity());
v.set_len(new_len);
initial_len
}
#[cfg(feature = "alloc")]
#[inline]
pub fn encode_lower<T: ?Sized + AsRef<[u8]>>(input: &T) -> String {
encode_to_string(input.as_ref(), EncodeLower)
}
#[cfg(feature = "alloc")]
#[inline]
pub fn encode_upper<T: ?Sized + AsRef<[u8]>>(input: &T) -> String {
encode_to_string(input.as_ref(), EncodeUpper)
}
#[cfg(feature = "alloc")]
#[inline]
pub fn encode_config<T: ?Sized + AsRef<[u8]>>(input: &T, cfg: EncConfig) -> String {
encode_to_string(input.as_ref(), cfg)
}
#[cfg(feature = "alloc")]
#[inline]
pub fn encode_config_buf<T: ?Sized + AsRef<[u8]>>(input: &T,
cfg: EncConfig,
dst: &mut String) -> usize {
let src = input.as_ref();
let bytes_to_write = encoded_size(src.len());
let mut buf = core::mem::replace(dst, String::new()).into_bytes();
let cur_size = unsafe { grow_vec_uninitialized(&mut buf, bytes_to_write) };
encode_slice_raw(src, cfg, &mut buf[cur_size..]);
debug_assert!(core::str::from_utf8(&buf).is_ok());
*dst = unsafe { String::from_utf8_unchecked(buf) };
bytes_to_write
}
#[inline]
pub fn encode_config_slice<T: ?Sized + AsRef<[u8]>>(input: &T,
cfg: EncConfig,
dst: &mut [u8]) -> usize {
let src = input.as_ref();
let need_size = encoded_size(src.len());
if dst.len() < need_size {
dest_too_small_enc(dst.len(), need_size);
}
encode_slice_raw(src, cfg, &mut dst[..need_size]);
need_size
}
#[inline]
pub fn encode_byte(byte: u8, cfg: EncConfig) -> [u8; 2] {
let lut = if cfg == EncodeLower { HEX_LOWER } else { HEX_UPPER };
let lo = lut[(byte & 15) as usize];
let hi = lut[(byte >> 4) as usize];
[hi, lo]
}
#[inline]
pub fn encode_byte_l(byte: u8) -> [u8; 2] {
encode_byte(byte, EncodeLower)
}
#[inline]
pub fn encode_byte_u(byte: u8) -> [u8; 2] {
encode_byte(byte, EncodeUpper)
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum DecodeError {
InvalidByte {
index: usize,
byte: u8
},
InvalidLength {
length: usize
},
}
#[cold]
fn invalid_length(length: usize) -> DecodeError {
DecodeError::InvalidLength { length }
}
#[cold]
fn invalid_byte(index: usize, src: &[u8]) -> DecodeError {
DecodeError::InvalidByte { index, byte: src[index] }
}
impl core::fmt::Display for DecodeError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match *self {
DecodeError::InvalidByte { index, byte } => {
write!(f, "Invalid byte `b{:?}`, at index {}.",
byte as char, index)
}
DecodeError::InvalidLength { length } =>
write!(f, "Base16 data cannot have length {} (must be even)",
length),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for DecodeError {
fn description(&self) -> &str {
match *self {
DecodeError::InvalidByte { .. } => "Illegal byte in base16 data",
DecodeError::InvalidLength { .. } => "Illegal length for base16 data",
}
}
fn cause(&self) -> Option<&dyn std::error::Error> {
None
}
}
#[inline]
fn decode_slice_raw(src: &[u8], dst: &mut[u8]) -> Result<(), usize> {
debug_assert!(src.len() / 2 == dst.len());
debug_assert!((src.len() & 1) == 0);
src.chunks_exact(2).enumerate().zip(dst.iter_mut()).try_for_each(|((si, s), d)| {
let r0 = DECODE_LUT[s[0] as usize];
let r1 = DECODE_LUT[s[1] as usize];
if (r0 | r1) >= 0 {
*d = ((r0 << 4) | r1) as u8;
Ok(())
} else {
Err(si * 2)
}
}).map_err(|bad_idx| raw_decode_err(bad_idx, src))
}
#[cold]
#[inline(never)]
fn raw_decode_err(idx: usize, src: &[u8]) -> usize {
let b0 = src[idx];
if decode_byte(b0).is_none() {
idx
} else {
idx + 1
}
}
#[cfg(feature = "alloc")]
#[inline]
pub fn decode<T: ?Sized + AsRef<[u8]>>(input: &T) -> Result<Vec<u8>, DecodeError> {
let src = input.as_ref();
if (src.len() & 1) != 0 {
return Err(invalid_length(src.len()));
}
let need_size = src.len() >> 1;
let mut dst = Vec::with_capacity(need_size);
unsafe { dst.set_len(need_size); }
match decode_slice_raw(src, &mut dst) {
Ok(()) => Ok(dst),
Err(index) => Err(invalid_byte(index, src))
}
}
#[cfg(feature = "alloc")]
#[inline]
pub fn decode_buf<T: ?Sized + AsRef<[u8]>>(input: &T, v: &mut Vec<u8>) -> Result<usize, DecodeError> {
let src = input.as_ref();
if (src.len() & 1) != 0 {
return Err(invalid_length(src.len()));
}
let mut work = core::mem::replace(v, Vec::default());
let need_size = src.len() >> 1;
let current_size = unsafe {
grow_vec_uninitialized(&mut work, need_size)
};
match decode_slice_raw(src, &mut work[current_size..]) {
Ok(()) => {
core::mem::swap(v, &mut work);
Ok(need_size)
}
Err(index) => {
work.truncate(current_size);
core::mem::swap(v, &mut work);
Err(invalid_byte(index, src))
}
}
}
#[inline]
pub fn decode_slice<T: ?Sized + AsRef<[u8]>>(input: &T, out: &mut [u8]) -> Result<usize, DecodeError> {
let src = input.as_ref();
if (src.len() & 1) != 0 {
return Err(invalid_length(src.len()));
}
let need_size = src.len() >> 1;
if out.len() < need_size {
dest_too_small_dec(out.len(), need_size);
}
match decode_slice_raw(src, &mut out[..need_size]) {
Ok(()) => Ok(need_size),
Err(index) => Err(invalid_byte(index, src))
}
}
#[inline]
pub fn decode_byte(c: u8) -> Option<u8> {
if c.wrapping_sub(b'0') <= 9 {
Some(c.wrapping_sub(b'0'))
} else if c.wrapping_sub(b'a') < 6 {
Some(c.wrapping_sub(b'a') + 10)
} else if c.wrapping_sub(b'A') < 6 {
Some(c.wrapping_sub(b'A') + 10)
} else {
None
}
}
static HEX_UPPER: [u8; 16] = *b"0123456789ABCDEF";
static HEX_LOWER: [u8; 16] = *b"0123456789abcdef";
static DECODE_LUT: [i8; 256] = [
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5,
6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1
];
#[inline(never)]
#[cold]
fn usize_overflow(len: usize) -> ! {
panic!("usize overflow when computing size of destination: {}", len);
}
#[cold]
#[inline(never)]
fn dest_too_small_enc(dst_len: usize, need_size: usize) -> ! {
panic!("Destination is not large enough to encode input: {} < {}", dst_len, need_size);
}
#[cold]
#[inline(never)]
fn dest_too_small_dec(dst_len: usize, need_size: usize) -> ! {
panic!("Destination buffer not large enough for decoded input {} < {}", dst_len, need_size);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
#[cfg(pointer_size )]
fn test_encoded_size_panic_top_bit() {
#[cfg(target_pointer_width = "64")]
let usz = 0x8000_0000_0000_0000usize;
#[cfg(target_pointer_width = "32")]
let usz = 0x8000_0000usize;
let _ = encoded_size(usz);
}
#[test]
#[should_panic]
fn test_encoded_size_panic_max() {
let _ = encoded_size(usize::max_value());
}
#[test]
fn test_encoded_size_allows_almost_max() {
#[cfg(target_pointer_width = "64")]
let usz = 0x7fff_ffff_ffff_ffffusize;
#[cfg(target_pointer_width = "32")]
let usz = 0x7fff_ffffusize;
assert_eq!(encoded_size(usz), usz * 2);
}
}