twoken 0.4.0

Generate One-Time Passwords from stored token secrets.
Documentation
use crc;
use serde::{de, Deserializer, Serializer};
use std::fmt;

// for yubico keys, listed as CRC-16/IBM-SDLC or X-25 on
// https://reveng.sourceforge.io/crc-catalogue/all.htm
const CRC_YUBICO: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);

const MODHEX: [char; 16] = [
    'c', 'b', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'r', 't', 'u', 'v',
];

pub fn get_crc(bytes: &[u8]) -> u16 {
    let mut digest = CRC_YUBICO.digest();
    digest.update(bytes);
    return digest.finalize();
}

pub fn to_modkex(bytes: &[u8]) -> String {
    let mut output = String::with_capacity(bytes.len() * 2);
    for byte in bytes {
        output.push(MODHEX[(byte >> 4) as usize]);
        output.push(MODHEX[(byte & 0xf) as usize]);
    }
    output
}

pub fn serialize_hex<S>(array: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    serializer.serialize_str(&hex::encode(array))
}

pub fn deserialize_hex<'de, D, const N: usize>(deserializer: D) -> Result<[u8; N], D::Error>
where
    D: Deserializer<'de>,
{
    struct StringVisitor<const M: usize>;

    impl<'de, const M: usize> de::Visitor<'de> for StringVisitor<M> {
        type Value = [u8; M];
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("a hex string")
        }

        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
        where
            E: de::Error,
        {
            let mut output = [0u8; M];
            hex::decode_to_slice(v, &mut output).map_err(E::custom)?;
            Ok(output)
        }
    }
    deserializer.deserialize_any(StringVisitor::<N>)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn crc_check() {
        assert_eq!(get_crc(b"123456789"), 0x906e)
    }

    #[test]
    fn test_modhex() {
        assert_eq!(to_modkex(&[0xaf, 0x1d, 0xc5]), "lvbtrg")
    }
}