base32.rs
1 /* This file is part of DarkFi (https://dark.fi) 2 * 3 * Copyright (C) 2020-2025 Dyne.org foundation 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation, either version 3 of the 8 * License, or (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU Affero General Public License for more details. 14 * 15 * You should have received a copy of the GNU Affero General Public License 16 * along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19 //! Base32 encoding as specified by RFC4648 20 //! Optional padding is the `=` character. 21 // Taken from https://github.com/andreasots/base32 22 use core::cmp::min; 23 24 /// Standard Base32 alphabet. 25 const ENCODE_STD: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; 26 27 /// Encode a byte slice with the given base32 alphabet into a base32 string. 28 pub fn encode(padding: bool, data: &[u8]) -> String { 29 let mut ret = Vec::with_capacity(data.len().div_ceil(4) * 5); 30 31 for chunk in data.chunks(5) { 32 let buf = { 33 let mut buf = [0u8; 5]; 34 for (i, &b) in chunk.iter().enumerate() { 35 buf[i] = b; 36 } 37 buf 38 }; 39 40 ret.push(ENCODE_STD[((buf[0] & 0xf8) >> 3) as usize]); 41 ret.push(ENCODE_STD[(((buf[0] & 0x07) << 2) | ((buf[1] & 0xc0) >> 6)) as usize]); 42 ret.push(ENCODE_STD[((buf[1] & 0x3e) >> 1) as usize]); 43 ret.push(ENCODE_STD[(((buf[1] & 0x01) << 4) | ((buf[2] & 0xf0) >> 4)) as usize]); 44 ret.push(ENCODE_STD[(((buf[2] & 0x0f) << 1) | (buf[3] >> 7)) as usize]); 45 ret.push(ENCODE_STD[((buf[3] & 0x7c) >> 2) as usize]); 46 ret.push(ENCODE_STD[(((buf[3] & 0x03) << 3) | ((buf[4] & 0xe0) >> 5)) as usize]); 47 ret.push(ENCODE_STD[(buf[4] & 0x1f) as usize]); 48 } 49 50 if data.len() % 5 != 0 { 51 let len = ret.len(); 52 let num_extra = 8 - (data.len() % 5 * 8).div_ceil(5); 53 if padding { 54 for i in 1..num_extra + 1 { 55 ret[len - i] = b'='; 56 } 57 } else { 58 ret.truncate(len - num_extra); 59 } 60 } 61 62 String::from_utf8(ret).unwrap() 63 } 64 65 const STD_INV_ALPHABET: [i8; 43] = [ 66 -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 67 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 68 ]; 69 70 /// Tries to decode a base32 string into a byte vector. Returns `None` if 71 /// something fails. 72 pub fn decode(data: &str) -> Option<Vec<u8>> { 73 if !data.is_ascii() { 74 return None 75 } 76 77 let data = data.as_bytes(); 78 let mut unpadded_data_len = data.len(); 79 80 for i in 1..min(6, data.len()) + 1 { 81 if data[data.len() - i] != b'=' { 82 break 83 } 84 unpadded_data_len -= 1; 85 } 86 87 let output_length = unpadded_data_len * 5 / 8; 88 let mut ret = Vec::with_capacity(output_length.div_ceil(5) * 5); 89 90 for chunk in data.chunks(8) { 91 let buf = { 92 let mut buf = [0u8; 8]; 93 for (i, &c) in chunk.iter().enumerate() { 94 match STD_INV_ALPHABET.get(c.to_ascii_uppercase().wrapping_sub(b'0') as usize) { 95 Some(&-1) | None => return None, 96 Some(&value) => buf[i] = value as u8, 97 }; 98 } 99 100 buf 101 }; 102 103 ret.push((buf[0] << 3) | (buf[1] >> 2)); 104 ret.push((buf[1] << 6) | (buf[2] << 1) | (buf[3] >> 4)); 105 ret.push((buf[3] << 4) | (buf[4] >> 1)); 106 ret.push((buf[4] << 7) | (buf[5] << 2) | (buf[6] >> 3)); 107 ret.push((buf[6] << 5) | buf[7]); 108 } 109 110 ret.truncate(output_length); 111 Some(ret) 112 } 113 114 #[cfg(test)] 115 mod tests { 116 #[test] 117 fn base32_encoding_decoding() { 118 let s = b"b32Test"; // This should pad with 4 = 119 let encoded = super::encode(true, &s[..]); 120 assert_eq!(&encoded, "MIZTEVDFON2A===="); 121 assert_eq!(super::decode(&encoded).unwrap(), s); 122 123 let s = b"b32Testoor"; // This shouldn't pad 124 let encoded = super::encode(true, &s[..]); 125 assert_eq!(&encoded, "MIZTEVDFON2G633S"); 126 assert_eq!(super::decode(&encoded).unwrap(), s); 127 } 128 }