use crate::error::{Error, ErrorCode};
const BASE38_CHARS: [char; 38] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.',
];
const UNUSED: u8 = 255;
const DECODE_BASE38: [u8; 46] = [
36, 37, UNUSED, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, ];
const RADIX: u32 = BASE38_CHARS.len() as u32;
pub fn encode_string<const N: usize>(bytes: &[u8]) -> Result<heapless::String<N>, Error> {
let mut string = heapless::String::new();
for c in encode(bytes) {
string.push(c).map_err(|_| ErrorCode::NoSpace)?;
}
Ok(string)
}
pub fn encode(bytes: &[u8]) -> impl Iterator<Item = char> + '_ {
(0..bytes.len() / 3)
.flat_map(move |index| {
let offset = index * 3;
encode_base38(
((bytes[offset + 2] as u32) << 16)
| ((bytes[offset + 1] as u32) << 8)
| (bytes[offset] as u32),
5,
)
})
.chain(
core::iter::once(bytes.len() % 3).flat_map(move |remainder| {
let offset = bytes.len() / 3 * 3;
match remainder {
2 => encode_base38(
((bytes[offset + 1] as u32) << 8) | (bytes[offset] as u32),
4,
),
1 => encode_base38(bytes[offset] as u32, 2),
_ => encode_base38(0, 0),
}
}),
)
}
fn encode_base38(mut value: u32, repeat: usize) -> impl Iterator<Item = char> {
(0..repeat).map(move |_| {
let remainder = value % RADIX;
let c = BASE38_CHARS[remainder as usize];
value = (value - remainder) / RADIX;
c
})
}
pub fn decode_vec<const N: usize>(base38_str: &str) -> Result<heapless::Vec<u8, N>, Error> {
let mut vec = heapless::Vec::new();
for byte in decode(base38_str) {
vec.push(byte?).map_err(|_| ErrorCode::NoSpace)?;
}
Ok(vec)
}
pub fn decode(base38_str: &str) -> impl Iterator<Item = Result<u8, Error>> + '_ {
let stru = base38_str.as_bytes();
(0..stru.len() / 5)
.flat_map(move |index| {
let offset = index * 5;
decode_base38(&stru[offset..offset + 5])
})
.chain({
let offset = stru.len() / 5 * 5;
decode_base38(&stru[offset..])
})
.take_while(Result::is_ok)
}
fn decode_base38(chars: &[u8]) -> impl Iterator<Item = Result<u8, Error>> {
let mut value = 0u32;
let mut cerr = None;
let repeat = match chars.len() {
5 => 3,
4 => 2,
2 => 1,
0 => 0,
_ => -1,
};
if repeat >= 0 {
for c in chars.iter().rev() {
match decode_char(*c) {
Ok(v) => value = value * RADIX + v as u32,
Err(err) => {
cerr = Some(err.code());
break;
}
}
}
} else {
cerr = Some(ErrorCode::InvalidData)
}
(0..repeat)
.map(move |_| {
if let Some(err) = cerr {
Err(err.into())
} else {
let byte = (value & 0xff) as u8;
value >>= 8;
Ok(byte)
}
})
.take_while(Result::is_ok)
}
fn decode_char(c: u8) -> Result<u8, Error> {
if !(45..=90).contains(&c) {
Err(ErrorCode::InvalidData)?;
}
let c = DECODE_BASE38[c as usize - 45];
if c == UNUSED {
Err(ErrorCode::InvalidData)?;
}
Ok(c)
}
#[cfg(test)]
mod tests {
use super::*;
const ENCODED: &str = "-MOA57ZU02IT2L2BJ00";
const DECODED: [u8; 11] = [
0x88, 0xff, 0xa7, 0x91, 0x50, 0x40, 0x00, 0x47, 0x51, 0xdd, 0x02,
];
#[test]
fn can_base38_encode() {
assert_eq!(
encode_string::<{ ENCODED.len() }>(&DECODED).unwrap(),
ENCODED
);
}
#[test]
fn can_base38_decode() {
assert_eq!(
decode_vec::<{ DECODED.len() }>(ENCODED).expect("Cannot decode base38"),
DECODED
);
}
}