binarytext 0.1.2

Binary-to-text encoders / decoders
Documentation
//! Implemention of the Base45 encoder.

use crate::binarytext::BinaryText;
use crate::error::BinTxtError;

/// Implements the Base45 algorithm as proposed in RFC 9285
#[derive(Clone, Debug)]
pub struct Base45 {}

impl Default for Base45 {
    fn default() -> Self {
        Self::new()
    }
}

impl Base45 {
    pub fn new() -> Self {
        Self {}
    }
}

impl BinaryText for Base45 {
    fn base(&self) -> usize {
        45
    }

    fn name(&self) -> &str {
        "Base45"
    }

    fn n_bytes_encode(&self) -> usize {
        2
    }

    fn n_bytes_decode(&self) -> usize {
        3
    }

    fn encode_byte(&self, byte: u8) -> Result<u8, BinTxtError> {
        const LUT: [u8; 45] = [
            b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D',
            b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R',
            b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b' ', b'$', b'%', b'*', b'+', b'-',
            b'.', b'/', b':',
        ];
        if byte >= 45 {
            let msg = format!("Byte {byte} exceeds maximum {}", 45);
            return Err(BinTxtError::EncodingErr(msg));
        }
        Ok(LUT[byte as usize])
    }

    fn encode_into_vec(&self, input: &[u8], res: &mut Vec<u8>) -> Result<(), BinTxtError> {
        const BASE: [u16; 3] = [1u16, 45u16, 45u16 * 45u16];
        res.clear();
        let iter = input.chunks_exact(2);
        let bytes_rem = iter.remainder();
        for bytes in iter {
            let mut val = (bytes[0] as u16) << 8 | (bytes[1] as u16);
            // Compute the three bytes satisfying val = b0 * 45**0 + b1 * 45**1 + b2 * 45**2
            let mut arr = [u8::MAX; 3];
            let tmp = val / BASE[2];
            arr[2] = tmp as u8;
            val -= tmp * BASE[2];
            let tmp = val / BASE[1];
            arr[1] = tmp as u8;
            val -= tmp * BASE[1];
            arr[0] = val as u8;
            // Write the actual characters of the byte to the string
            for b in arr {
                let b = self.encode_byte(b)?;
                res.push(b);
            }
        }
        // If there is one byte left, it is encoded by two characters
        if !bytes_rem.is_empty() {
            let mut val = bytes_rem[0] as u16;
            let mut arr = [u8::MAX; 2];
            let tmp = val / BASE[1];
            arr[1] = tmp as u8;
            val -= tmp * BASE[1];
            arr[0] = val as u8;
            for a in arr {
                let b = self.encode_byte(a)?;
                res.push(b);
            }
        }
        Ok(())
    }

    fn decode_byte(&self, byte: u8) -> Result<u8, BinTxtError> {
        const LUT: [u8; 96] = [
            255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
            255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 36, 255,
            255, 255, 37, 38, 255, 255, 255, 255, 39, 40, 255, 41, 42, 43, 0, 1, 2, 3, 4, 5, 6, 7,
            8, 9, 44, 255, 255, 255, 255, 255, 255, 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, 255, 255, 255, 255, 255,
        ];
        let b = if byte < 96 { LUT[byte as usize] } else { 255 };
        if b < 255 {
            Ok(b)
        } else {
            let errmsg = format!("Invalid byte \"{}\" in Base45 string", byte);
            Err(BinTxtError::DecodingErr(errmsg))
        }
    }

    fn decode_into_vec(&self, input: &[u8], res: &mut Vec<u8>) -> Result<(), BinTxtError> {
        const BASE: [u16; 3] = [1u16, 45u16, 45u16 * 45u16];
        res.clear();
        let iter = input.chunks_exact(3);
        let bytes_rem = iter.remainder();
        // Go through the string in chunks of 3
        for bytes in iter {
            let dec = self.decode_byte(bytes[0])?;
            let mut val = dec as u16;
            let dec = self.decode_byte(bytes[1])?;
            val += dec as u16 * BASE[1];
            let dec = self.decode_byte(bytes[2])?;
            val += dec as u16 * BASE[2];
            let b0 = (val >> 8) as u8;
            res.push(b0);
            let b1 = val as u8;
            res.push(b1);
        }
        // Handle the last two bytes if there is a remainder
        if bytes_rem.len() == 2 {
            let mut val = 0u16;
            let dec = self.decode_byte(bytes_rem[0])?;
            val += dec as u16;
            let dec = self.decode_byte(bytes_rem[1])?;
            val += dec as u16 * BASE[1];
            let b = val as u8;
            res.push(b);
        }
        if bytes_rem.len() == 1 {
            let errmsg = "Invalid remainder of length one in Base45 string".to_string();
            return Err(BinTxtError::DecodingErr(errmsg));
        }
        Ok(())
    }

    fn is_decodable(&self, input: &str) -> bool {
        for &byte in input.as_bytes() {
            match self.decode_byte(byte) {
                Ok(_) => {}
                Err(_) => {
                    return false;
                }
            }
        }
        true
    }
}