#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
use std::io;
use std::i64;
const SHIFT: u8 = 5;
const MASK: u8 = (1 << SHIFT) - 1;
const CONTINUED: u8 = 1 << SHIFT;
#[derive(Debug)]
pub enum Error {
UnexpectedEof,
InvalidBase64(u8),
Overflow,
}
pub type Result<T> = std::result::Result<T, Error>;
fn decode64(input: u8) -> Result<u8> {
match input {
b'A'...b'Z' => Ok(input - b'A'),
b'a'...b'z' => Ok(input - b'a' + 26),
b'0'...b'9' => Ok(input - b'0' + 52),
b'+' => Ok(62),
b'/' => Ok(63),
_ => Err(Error::InvalidBase64(input)),
}
}
pub fn decode<B>(input: &mut B) -> Result<i64>
where
B: Iterator<Item = u8>,
{
let mut accum: u64 = 0;
let mut shift = 0;
let mut keep_going = true;
while keep_going {
let byte = input.next().ok_or(Error::UnexpectedEof)?;
let digit = decode64(byte)?;
keep_going = (digit & CONTINUED) != 0;
let digit_value = ((digit & MASK) as u64)
.checked_shl(shift as u32)
.ok_or(Error::Overflow)?;
accum = accum.checked_add(digit_value).ok_or(Error::Overflow)?;
shift += SHIFT;
}
let abs_value = accum / 2;
if abs_value > (i64::MAX as u64) {
return Err(Error::Overflow);
}
if (accum & 1) != 0 {
Ok(-(abs_value as i64))
} else {
Ok(abs_value as i64)
}
}
fn encode64(value: u8) -> u8 {
debug_assert!(value < 64);
if value < 26 {
value + b'A'
} else if value < 52 {
value - 26 + b'a'
} else if value < 62 {
value - 52 + b'0'
} else if value == 62 {
b'+'
} else {
assert!(value == 63);
b'/'
}
}
pub fn encode<W>(value: i64, output: &mut W) -> io::Result<()>
where
W: io::Write,
{
let signed = value < 0;
let mut value = (value.wrapping_abs() as u64) << 1;
if signed {
if value == 0 {
value = (i64::MAX as u64) + 1;
}
value |= 1;
}
loop {
let mut digit = value as u8 & MASK;
value >>= SHIFT;
if value > 0 {
digit |= CONTINUED;
}
let bytes = [encode64(digit)];
output.write_all(&bytes[..])?;
if value == 0 {
break;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
#[test]
fn test_simple_roundtrip() {
for val in 0..64 {
assert_eq!(val, super::decode64(super::encode64(val)).unwrap());
}
}
}