pub mod read;
pub mod ring;
pub mod write;
pub(crate) mod copier;
#[cfg(test)]
mod tests;
pub const KEYMAP: &[u8; 91] =
b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+,-./:;<=>?@[]^_`{|}~";
const DIGITMAP: [u8; 94] = [
62, 0xFF, 63, 64, 65, 66, 0xFF, 67, 68, 69, 70, 71, 72, 73, 74, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
75, 76, 77, 78, 79, 80, 81, 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, 82, 0xFF, 83, 84, 85, 86, 36, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 87, 88, 89, 90,
];
#[derive(Debug, Clone, Copy)]
pub enum DecodeError {
InvalidCode { index: usize, code: u8 },
TooLargeChunk {
index: usize,
chunk: [u8; 16],
decoded: u128,
},
TooMuchPadding { index: usize, padded: usize },
UnpaddedTail { index: usize },
}
impl DecodeError {
fn nth_chunk(self, n: usize) -> Self {
match self {
DecodeError::InvalidCode { index, code } => DecodeError::InvalidCode {
index: index + n * 16,
code,
},
DecodeError::TooLargeChunk {
index,
chunk,
decoded,
} => DecodeError::TooLargeChunk {
index: index + n * 16,
chunk,
decoded,
},
DecodeError::TooMuchPadding { index, padded } => Self::TooMuchPadding {
index: index + 16 * n,
padded,
},
DecodeError::UnpaddedTail { index } => Self::UnpaddedTail {
index: index + 16 * n,
},
}
}
}
impl std::fmt::Display for DecodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidCode { index, code } => {
write!(
f,
"invalid base91le code \'{}\' at {index}",
code.escape_ascii()
)
}
Self::TooLargeChunk {
index,
chunk,
decoded,
} => write!(
f,
"too large chunk \"{}\"(0X{decoded:X}) start at {index} ,expected \".+/Nd*DP8>OFnJg]\"(0XFFFFFFFFFFFFFFFFFFFFFFFFFF) at most",
chunk.escape_ascii()
),
Self::TooMuchPadding { index, padded } => write!(
f,
"too much padding char({padded}) at {index}, expected at most 13"
),
Self::UnpaddedTail { index } => write!(f, "input ended at {index} not padded with '~'"),
}
}
}
impl std::error::Error for DecodeError {}
pub const fn encode_chunk(input: &[u8; 13]) -> [u8; 16] {
let mut input_arr = [0; 16];
unsafe { std::slice::from_raw_parts_mut(&raw mut input_arr[0], 13) }.copy_from_slice(input);
let mut input_num = u128::from_le_bytes(input_arr);
let mut out = [0; 16];
let mut idx = 0;
while idx < 16 {
out[idx] = KEYMAP[(input_num % 91) as usize];
input_num /= 91;
idx += 1;
}
out
}
fn encode_tail(input: &[u8]) -> [u8; 16] {
let len = input.len();
let mut input_padded = [0; 13];
input_padded[..len].copy_from_slice(input);
let mut out = encode_chunk(&input_padded);
out[len + 3..].copy_from_slice(&b"~~~~~~~~~~~~~"[len..]);
out
}
pub fn encode(input: &[u8]) -> String {
let (chunks, remainder) = input.as_chunks();
let capacity = (chunks.len() + (!remainder.is_empty() as usize)) * 16;
let mut vec = Vec::with_capacity(capacity);
for chunk in chunks {
vec.extend_from_slice(&encode_chunk(chunk));
}
if !remainder.is_empty() {
vec.extend_from_slice(&encode_tail(remainder));
}
unsafe { String::from_utf8_unchecked(vec) }
}
pub fn decode_chunk(input: &[u8; 16], buf: &mut [u8; 13]) -> Result<usize, DecodeError> {
const INVALID: u128 = 1 << (13 * 8);
let padded = input.iter().rev().take_while(|l| **l == b'~').count();
let len = 13usize
.checked_sub(padded)
.ok_or(DecodeError::TooMuchPadding {
index: 16 - padded,
padded,
})?;
let mut decoded = 0;
let mut exp = 1;
for (index, &code) in input[..len + 3].iter().enumerate() {
if code <= 0x20 || 0x7F <= code || code == b'"' || code == b'\'' || code == b'\\' {
return Err(DecodeError::InvalidCode { index, code });
}
decoded += DIGITMAP[(code - 0x21) as usize] as u128 * exp;
exp *= 91;
}
if decoded > INVALID {
return Err(DecodeError::TooLargeChunk {
index: 0,
chunk: *input,
decoded,
});
}
let out = decoded.to_le_bytes();
buf[..len].copy_from_slice(&out[..len]);
Ok(len)
}
pub fn decode(bytes: &str) -> Result<Vec<u8>, DecodeError> {
let (chunks, remainder) = bytes.as_bytes().as_chunks();
if !remainder.is_empty() {
return Err(DecodeError::UnpaddedTail { index: bytes.len() });
}
decode_chunks(chunks)
}
pub fn decode_chunks(input: &[[u8; 16]]) -> Result<Vec<u8>, DecodeError> {
let mut v = Vec::with_capacity(input.len() * 13);
let mut buf = [0; 13];
for (idx, chunk) in input.iter().enumerate() {
let n = decode_chunk(chunk, &mut buf).map_err(|err| err.nth_chunk(idx))?;
v.extend_from_slice(&buf[..n]);
}
Ok(v)
}