use alloc::string::String;
use alloc::vec::Vec;
const B85_DARK_MAX: i32 = 80;
#[inline]
fn numeral(dn: u8) -> u8 {
if dn < 4 { dn + b'#' } else { dn - 4 + b'*' }
}
#[inline]
fn b85_class(c: u8) -> usize {
(c >= b'#') as usize + (c > b'&') as usize + (c >= b'*') as usize + (c > b'z') as usize
}
#[inline]
fn is_b85(c: u8) -> bool {
b85_class(c) & 1 != 0
}
#[inline]
fn b85_dnos(c: u8) -> u8 {
const OFF: [u8; 5] = [0, b'#', 0, b'*' - 4, 0];
OFF[b85_class(c)]
}
pub fn encode(input: &[u8]) -> String {
let mut out: Vec<u8> = Vec::with_capacity(input.len() * 5 / 4 + input.len() / 64 + 4);
let mut n_col: i32 = 0;
let mut p_in = input;
while p_in.len() >= 4 {
let mut qbv: u32 = ((p_in[0] as u32) << 24)
| ((p_in[1] as u32) << 16)
| ((p_in[2] as u32) << 8)
| (p_in[3] as u32);
let mut quad = [0u8; 5];
let mut nco = 5usize;
while nco > 0 {
let nqv = qbv / 85;
let dv = (qbv - 85 * nqv) as u8;
qbv = nqv;
nco -= 1;
quad[nco] = numeral(dv);
}
out.extend_from_slice(&quad);
p_in = &p_in[4..];
n_col += 5;
if n_col >= B85_DARK_MAX {
out.push(b'\n');
n_col = 0;
}
}
let nb_in = p_in.len();
if nb_in > 0 {
let nco0 = nb_in + 1;
let mut qv: u64 = p_in[0] as u64;
let mut idx = 1usize;
let mut nbe = 1usize;
while {
let cont = nbe < nb_in;
nbe += 1;
cont
} {
qv = (qv << 8) | p_in[idx] as u64;
idx += 1;
}
n_col += nco0 as i32;
let mut tail = alloc::vec![0u8; nco0];
let mut nco = nco0;
while nco > 0 {
let dv = (qv % 85) as u8;
qv /= 85;
nco -= 1;
tail[nco] = numeral(dv);
}
out.extend_from_slice(&tail);
}
if n_col > 0 {
out.push(b'\n');
}
String::from_utf8(out).unwrap_or_default()
}
pub fn decode(input: &[u8]) -> Vec<u8> {
const NBOI: [i32; 6] = [0, 0, 1, 2, 3, 4];
let mut out = Vec::with_capacity(input.len() * 4 / 5 + 4);
let mut nc_in = input.len();
if nc_in > 0 && input[nc_in - 1] == b'\n' {
nc_in -= 1;
}
let mut pos = 0usize;
while nc_in > 0 {
while nc_in > 0 && input[pos] != 0 && !is_b85(input[pos]) {
pos += 1;
nc_in -= 1;
}
if nc_in == 0 {
break;
}
let mut nti = if nc_in > 5 { 5 } else { nc_in } as i32;
let mut nbo = NBOI[nti as usize];
if nbo == 0 {
break;
}
let mut qv: u64 = 0;
while nti > 0 {
let c = input[pos];
pos += 1;
nc_in -= 1;
let cdo = b85_dnos(c);
if cdo == 0 {
break;
}
qv = 85 * qv + (c - cdo) as u64;
nti -= 1;
}
nbo -= nti; if nbo >= 4 {
out.push((qv >> 24) as u8);
}
if nbo >= 3 {
out.push((qv >> 16) as u8);
}
if nbo >= 2 {
out.push((qv >> 8) as u8);
}
if nbo >= 1 {
out.push(qv as u8);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip() {
for len in 0..40usize {
let msg: Vec<u8> = (0..len).map(|i| (i * 7 + 1) as u8).collect();
let enc = encode(&msg);
assert_eq!(decode(enc.as_bytes()), msg, "len {len}");
}
}
#[test]
fn empty() {
assert_eq!(encode(b""), "");
assert_eq!(decode(b""), b"");
}
#[test]
fn wraps_at_80() {
let enc = encode(&[0u8; 70]);
let lines: Vec<&str> = enc.trim_end().split('\n').collect();
assert_eq!(lines[0].len(), 80);
assert_eq!(lines[1].len(), 8);
assert!(enc.ends_with('\n'));
}
#[test]
fn decode_skips_separators() {
let msg = [1u8, 2, 3, 4, 5, 6, 7, 8];
assert_eq!(decode(encode(&msg).as_bytes()), msg);
}
}