rust-auth-utils 1.0.0

A rust port of @better-auth/utils.
Documentation
// based on https://github.com/better-auth/utils/blob/main/src/base32.ts

use std::collections::HashMap;

/**
 * Returns the Base32 alphabet based on the encoding type.
 */
fn get_alphabet(hex: bool) -> &'static str {
    if hex {
        "0123456789ABCDEFGHIJKLMNOPQRSTUV"
    } else {
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
    }
}

/**
 * Creates a decode map for the given alphabet.
 */
fn create_decode_map(alphabet: &str) -> HashMap<char, u8> {
    alphabet
        .chars()
        .enumerate()
        .map(|(i, c)| (c, i as u8))
        .collect()
}

/**
 * Encodes a slice of bytes into a Base32 string.
 */
fn base32_encode(data: &[u8], alphabet: &str, padding: bool) -> String {
    if data.is_empty() {
        return String::new();
    }

    let mut result = String::with_capacity((data.len() * 8 + 4) / 5);
    let mut buffer = 0u16;
    let mut bits_left = 0;

    for &byte in data {
        buffer = (buffer << 8) | (byte as u16);
        bits_left += 8;

        while bits_left >= 5 {
            let val = (buffer >> (bits_left - 5)) & 0x1F;
            result.push(alphabet.chars().nth(val as usize).unwrap());
            bits_left -= 5;
        }
    }

    if bits_left > 0 {
        buffer <<= 5 - bits_left;
        let val = (buffer & 0x1F) as usize;
        result.push(alphabet.chars().nth(val).unwrap());
    }

    if padding {
        while (result.len() % 8) != 0 {
            result.push('=');
        }
    }

    result
}

/**
 * Decodes a Base32 string into a Vec<u8>.
 */
fn base32_decode(data: &str, alphabet: &str) -> Result<Vec<u8>, String> {
    let decode_map = create_decode_map(alphabet);
    let mut result = Vec::new();
    let mut buffer = 0u16;
    let mut bits_left = 0;

    for c in data.chars().filter(|&c| c != '=') {
        let val = decode_map
            .get(&c.to_ascii_uppercase())
            .ok_or_else(|| format!("Invalid Base32 character: {}", c))?;

        buffer = (buffer << 5) | (*val as u16);
        bits_left += 5;

        if bits_left >= 8 {
            result.push((buffer >> (bits_left - 8)) as u8);
            bits_left -= 8;
        }
    }

    if bits_left > 0 && bits_left >= 5 {
        buffer <<= 8 - bits_left;
        result.push((buffer >> (bits_left - 8)) as u8);
    }

    Ok(result)
}

#[derive(Default)]
pub struct Base32;

impl Base32 {
    /**
     * Encodes data into a Base32 string.
     */
    pub fn encode(data: &[u8], padding: Option<bool>) -> String {
        let alphabet = get_alphabet(false);
        base32_encode(data, alphabet, padding.unwrap_or(true))
    }

    /**
     * Decodes a Base32 string into a Vec<u8>.
     */
    pub fn decode(data: &str) -> Result<Vec<u8>, String> {
        let alphabet = get_alphabet(false);
        base32_decode(data, alphabet)
    }
}

#[derive(Default)]
pub struct Base32Hex;

impl Base32Hex {
    /**
     * Encodes data into a Base32hex string.
     */
    pub fn encode(data: &[u8], padding: Option<bool>) -> String {
        let alphabet = get_alphabet(true);
        base32_encode(data, alphabet, padding.unwrap_or(true))
    }

    /**
     * Decodes a Base32hex string into a Vec<u8>.
     */
    pub fn decode(data: &str) -> Result<Vec<u8>, String> {
        let alphabet = get_alphabet(true);
        base32_decode(data, alphabet)
    }
}