dcc-decode 0.1.0

Tool to read EU digital covid certificates
use std::{convert::TryFrom, error::Error, fmt};

#[derive(Debug)]
pub enum Base45Error {
    Unimplemented,
    InvalidChar(u8),
    InvalidTriple(u32),
}

impl Error for Base45Error {}
impl fmt::Display for Base45Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Unimplemented => write!(f, "Base45Error"),
            Self::InvalidChar(c) => write!(f, "Invalid character: {}", c),
            Self::InvalidTriple(t) => write!(f, "Invalid sum: {}", t),
        }
    }
}

pub fn base45_cval(input: u8) -> Result<u32, Base45Error> {
    match input {
        b'0'..=b'9' => Ok(u32::from(input - b'0')),
        b'A'..=b'Z' => Ok(u32::from(input - b'A') + 10),
        b' ' => Ok(36),
        b'$' => Ok(37),
        b'%' => Ok(38),
        b'*' => Ok(39),
        b'+' => Ok(40),
        b'-' => Ok(41),
        b'.' => Ok(42),
        b'/' => Ok(43),
        b':' => Ok(44),
        _ => Err(Base45Error::InvalidChar(input)),
    }
}

pub fn base45_cdec([c, d, e]: [u8; 3]) -> Result<[u8; 2], Base45Error> {
    let c = base45_cval(c)?;
    let d = base45_cval(d)?;
    let e = base45_cval(e)?;

    let sum = c + 45 * d + 45 * 45 * e;
    let r = u16::try_from(sum).map_err(|_| Base45Error::InvalidTriple(sum))?;
    Ok(r.to_le_bytes())
}

#[allow(clippy::many_single_char_names)]
pub fn base45_decode(input: &str) -> Result<Vec<u8>, Base45Error> {
    let bytes = input.as_bytes();
    let mut out = Vec::with_capacity(bytes.len() * 2 / 3 + 1);

    let mut triples = bytes.chunks_exact(3);
    while let Some(triple) = triples.next() {
        println!("{}", std::str::from_utf8(triple).unwrap());
        let c = triple[0];
        let d = triple[1];
        let e = triple[2];

        let [a, b] = base45_cdec([c, d, e])?;
        out.push(a);
        out.push(b);
    }
    if let [_c, _d] = triples.remainder() {
        return Err(Base45Error::Unimplemented);
    }

    Ok(out)
}