rust_auth_utils/
base32.rs

1// based on https://github.com/better-auth/utils/blob/main/src/base32.ts
2
3use std::collections::HashMap;
4
5/**
6 * Returns the Base32 alphabet based on the encoding type.
7 */
8fn get_alphabet(hex: bool) -> &'static str {
9    if hex {
10        "0123456789ABCDEFGHIJKLMNOPQRSTUV"
11    } else {
12        "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
13    }
14}
15
16/**
17 * Creates a decode map for the given alphabet.
18 */
19fn create_decode_map(alphabet: &str) -> HashMap<char, u8> {
20    alphabet
21        .chars()
22        .enumerate()
23        .map(|(i, c)| (c, i as u8))
24        .collect()
25}
26
27/**
28 * Encodes a slice of bytes into a Base32 string.
29 */
30fn base32_encode(data: &[u8], alphabet: &str, padding: bool) -> String {
31    if data.is_empty() {
32        return String::new();
33    }
34
35    let mut result = String::with_capacity((data.len() * 8 + 4) / 5);
36    let mut buffer = 0u16;
37    let mut bits_left = 0;
38
39    for &byte in data {
40        buffer = (buffer << 8) | (byte as u16);
41        bits_left += 8;
42
43        while bits_left >= 5 {
44            let val = (buffer >> (bits_left - 5)) & 0x1F;
45            result.push(alphabet.chars().nth(val as usize).unwrap());
46            bits_left -= 5;
47        }
48    }
49
50    if bits_left > 0 {
51        buffer <<= 5 - bits_left;
52        let val = (buffer & 0x1F) as usize;
53        result.push(alphabet.chars().nth(val).unwrap());
54    }
55
56    if padding {
57        while (result.len() % 8) != 0 {
58            result.push('=');
59        }
60    }
61
62    result
63}
64
65/**
66 * Decodes a Base32 string into a Vec<u8>.
67 */
68fn base32_decode(data: &str, alphabet: &str) -> Result<Vec<u8>, String> {
69    let decode_map = create_decode_map(alphabet);
70    let mut result = Vec::new();
71    let mut buffer = 0u16;
72    let mut bits_left = 0;
73
74    for c in data.chars().filter(|&c| c != '=') {
75        let val = decode_map
76            .get(&c.to_ascii_uppercase())
77            .ok_or_else(|| format!("Invalid Base32 character: {}", c))?;
78
79        buffer = (buffer << 5) | (*val as u16);
80        bits_left += 5;
81
82        if bits_left >= 8 {
83            result.push((buffer >> (bits_left - 8)) as u8);
84            bits_left -= 8;
85        }
86    }
87
88    if bits_left > 0 && bits_left >= 5 {
89        buffer <<= 8 - bits_left;
90        result.push((buffer >> (bits_left - 8)) as u8);
91    }
92
93    Ok(result)
94}
95
96#[derive(Default)]
97pub struct Base32;
98
99impl Base32 {
100    /**
101     * Encodes data into a Base32 string.
102     */
103    pub fn encode(data: &[u8], padding: Option<bool>) -> String {
104        let alphabet = get_alphabet(false);
105        base32_encode(data, alphabet, padding.unwrap_or(true))
106    }
107
108    /**
109     * Decodes a Base32 string into a Vec<u8>.
110     */
111    pub fn decode(data: &str) -> Result<Vec<u8>, String> {
112        let alphabet = get_alphabet(false);
113        base32_decode(data, alphabet)
114    }
115}
116
117#[derive(Default)]
118pub struct Base32Hex;
119
120impl Base32Hex {
121    /**
122     * Encodes data into a Base32hex string.
123     */
124    pub fn encode(data: &[u8], padding: Option<bool>) -> String {
125        let alphabet = get_alphabet(true);
126        base32_encode(data, alphabet, padding.unwrap_or(true))
127    }
128
129    /**
130     * Decodes a Base32hex string into a Vec<u8>.
131     */
132    pub fn decode(data: &str) -> Result<Vec<u8>, String> {
133        let alphabet = get_alphabet(true);
134        base32_decode(data, alphabet)
135    }
136}