/ src / util / encoding / base32.rs
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  }