fedimint_core/
base32.rs

1use std::collections::BTreeMap;
2
3use anyhow::Context;
4
5/// Lowercase RFC 4648 Base32hex alphabet (32 characters).
6const RFC4648: [u8; 32] = *b"0123456789abcdefghijklmnopqrstuv";
7
8/// Encodes the input bytes as Base32 (hex variant) using lowercase characters
9pub fn encode(input: &[u8]) -> String {
10    let mut output = Vec::with_capacity(((8 * input.len()) / 5) + 1);
11
12    let mut buffer = 0;
13    let mut bits = 0;
14
15    for byte in input {
16        buffer |= (*byte as usize) << bits;
17        bits += 8;
18
19        while bits >= 5 {
20            output.push(RFC4648[buffer & 0b11111]);
21
22            buffer >>= 5;
23            bits -= 5;
24        }
25    }
26
27    if bits > 0 {
28        output.push(RFC4648[buffer & 0b11111]);
29    }
30
31    String::from_utf8(output).unwrap()
32}
33
34/// Decodes a base 32 string back to raw bytes. Returns an error
35/// if any invalid character is encountered.
36pub fn decode(input: &str) -> anyhow::Result<Vec<u8>> {
37    let decode_table = RFC4648
38        .iter()
39        .enumerate()
40        .map(|(i, c)| (*c, i))
41        .collect::<BTreeMap<u8, usize>>();
42
43    let mut output = Vec::with_capacity(((5 * input.len()) / 8) + 1);
44
45    let mut buffer = 0;
46    let mut bits = 0;
47
48    for byte in input.as_bytes() {
49        let value = decode_table
50            .get(byte)
51            .copied()
52            .context("Invalid character encountered")?;
53
54        buffer |= value << bits;
55        bits += 5;
56
57        while bits >= 8 {
58            output.push((buffer & 0xFF) as u8);
59
60            buffer >>= 8;
61            bits -= 8;
62        }
63    }
64
65    Ok(output)
66}
67
68#[test]
69fn test_base_32_roundtrip() {
70    let data: [u8; 10] = [0x50, 0xAB, 0x3F, 0x77, 0x01, 0xCD, 0x55, 0xFE, 0x10, 0x99];
71
72    for n in 1..10 {
73        assert_eq!(decode(&encode(&data[0..n])).unwrap(), data[0..n]);
74    }
75}