use thiserror::Error;
const EN85: &[u8; 85] =
b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub enum DecodeError {
#[error("truncated base85 line")]
TruncatedLine,
#[error("invalid base85 alphabet byte {0}")]
InvalidAlphabetByte(u8),
#[error("base85 overflow")]
Overflow,
#[error("trailing base85 data")]
TrailingData,
}
fn prep_decode_table() -> [u8; 256] {
let mut de85 = [0u8; 256];
for (i, &c) in EN85.iter().enumerate() {
de85[c as usize] = (i + 1) as u8;
}
de85
}
#[must_use]
pub fn encode(mut data: &[u8]) -> String {
let mut result = String::new();
while !data.is_empty() {
let mut acc: u32 = 0;
let mut cnt = 24i32;
while cnt >= 0 {
let ch = u32::from(data[0]);
data = &data[1..];
acc |= ch << cnt;
if data.is_empty() {
break;
}
cnt -= 8;
}
let mut buf = [0u8; 5];
for i in (0..5).rev() {
let val = (acc % 85) as usize;
acc /= 85;
buf[i] = EN85[val];
}
result.extend(buf.iter().map(|b| char::from(*b)));
}
result
}
pub fn decode_body(buffer: &[u8], mut out_len: usize) -> Result<Vec<u8>, DecodeError> {
static DE85: std::sync::OnceLock<[u8; 256]> = std::sync::OnceLock::new();
let de85 = DE85.get_or_init(prep_decode_table);
let mut dst = Vec::with_capacity(out_len);
let mut pos = 0usize;
while out_len > 0 {
let mut acc: u32 = 0;
for _ in 0..4 {
let ch = *buffer.get(pos).ok_or(DecodeError::TruncatedLine)?;
pos += 1;
let de = de85[ch as usize];
if de == 0 {
return Err(DecodeError::InvalidAlphabetByte(ch));
}
acc = acc
.checked_mul(85)
.and_then(|a| a.checked_add(u32::from(de - 1)))
.ok_or(DecodeError::Overflow)?;
}
let ch = *buffer.get(pos).ok_or(DecodeError::TruncatedLine)?;
pos += 1;
let de = de85[ch as usize];
if de == 0 {
return Err(DecodeError::InvalidAlphabetByte(ch));
}
acc = acc
.checked_mul(85)
.and_then(|a| a.checked_add(u32::from(de - 1)))
.ok_or(DecodeError::Overflow)?;
let chunk = out_len.min(4);
out_len -= chunk;
let mut a = acc;
for _ in 0..chunk {
a = a.rotate_left(8);
dst.push(a as u8);
}
}
if pos != buffer.len() {
return Err(DecodeError::TrailingData);
}
Ok(dst)
}