taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

base32.rs (7041B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024-2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use std::{fmt::Display, ops::Deref, str::FromStr};
     18 
     19 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
     20 
     21 pub const CROCKFORD_ALPHABET: &[u8] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
     22 
     23 /** Encoded bytes len of Crockford's base32 */
     24 #[inline]
     25 const fn encoded_len(len: usize) -> usize {
     26     (len * 8).div_ceil(5)
     27 }
     28 
     29 /** Buffer bytes len of Crockford's base32 using a batch of 8 chars */
     30 #[inline]
     31 const fn encoded_buf_len(len: usize) -> usize {
     32     (len / 5 + 1) * 8
     33 }
     34 
     35 /** Encode bytes using Crockford's base32 */
     36 pub fn encode_static<'a, const N: usize>(bytes: &[u8; N], out: &'a mut [u8]) -> &'a str {
     37     // Batch encoded
     38     encode_batch(bytes, out);
     39 
     40     // Truncate incomplete ending chunk
     41     let truncated = &out[..encoded_len(bytes.len())];
     42 
     43     // SAFETY: only contains valid ASCII characters from CROCKFORD_ALPHABET
     44     unsafe { std::str::from_utf8_unchecked(truncated) }
     45 }
     46 
     47 /** Batch encode bytes using Crockford's base32 */
     48 #[inline]
     49 fn encode_batch(bytes: &[u8], encoded: &mut [u8]) {
     50     // Check buffer len
     51     assert!(encoded.len() >= encoded_buf_len(bytes.len()));
     52 
     53     // Encode chunks of 5B for 8 chars
     54     for (chunk, encoded) in bytes.chunks(5).zip(encoded.chunks_exact_mut(8)) {
     55         let mut buf = [0u8; 5];
     56         for (i, &b) in chunk.iter().enumerate() {
     57             buf[i] = b;
     58         }
     59         encoded[0] = CROCKFORD_ALPHABET[((buf[0] & 0xF8) >> 3) as usize];
     60         encoded[1] = CROCKFORD_ALPHABET[(((buf[0] & 0x07) << 2) | ((buf[1] & 0xC0) >> 6)) as usize];
     61         encoded[2] = CROCKFORD_ALPHABET[((buf[1] & 0x3E) >> 1) as usize];
     62         encoded[3] = CROCKFORD_ALPHABET[(((buf[1] & 0x01) << 4) | ((buf[2] & 0xF0) >> 4)) as usize];
     63         encoded[4] = CROCKFORD_ALPHABET[(((buf[2] & 0x0F) << 1) | (buf[3] >> 7)) as usize];
     64         encoded[5] = CROCKFORD_ALPHABET[((buf[3] & 0x7C) >> 2) as usize];
     65         encoded[6] = CROCKFORD_ALPHABET[(((buf[3] & 0x03) << 3) | ((buf[4] & 0xE0) >> 5)) as usize];
     66         encoded[7] = CROCKFORD_ALPHABET[(buf[4] & 0x1F) as usize];
     67     }
     68 }
     69 
     70 #[derive(Debug, thiserror::Error)]
     71 pub enum Base32Error<const N: usize> {
     72     #[error("invalid Crockford's base32 format")]
     73     Format,
     74     #[error("invalid length expected {N} bytes got {0}")]
     75     Length(usize),
     76 }
     77 
     78 /** Crockford's base32 inverse table, case insentitive and with substitution */
     79 const CROCKFORD_INV: [u8; 256] = {
     80     let mut table = [255; 256];
     81 
     82     // Fill the canonical alphabet
     83     let mut i = 0;
     84     while i < CROCKFORD_ALPHABET.len() {
     85         let b = CROCKFORD_ALPHABET[i];
     86         table[b as usize] = i as u8;
     87         i += 1;
     88     }
     89 
     90     // Add substitution
     91     table[b'O' as usize] = table[b'0' as usize];
     92     table[b'I' as usize] = table[b'1' as usize];
     93     table[b'L' as usize] = table[b'1' as usize];
     94     table[b'U' as usize] = table[b'V' as usize];
     95 
     96     // Make the table case insensitive
     97     let mut i = 0;
     98     while i < CROCKFORD_ALPHABET.len() {
     99         let b = CROCKFORD_ALPHABET[i];
    100         table[b.to_ascii_lowercase() as usize] = table[b as usize];
    101         i += 1;
    102     }
    103 
    104     table
    105 };
    106 
    107 /** Decoded bytes len of Crockford's base32 */
    108 #[inline]
    109 const fn decoded_len(len: usize) -> usize {
    110     len * 5 / 8
    111 }
    112 
    113 /** Buffer bytes len of Crockford's base32 using a batch of 5 bytes */
    114 #[inline]
    115 const fn decoded_buf_len(len: usize) -> usize {
    116     (len / 8 + 1) * 5
    117 }
    118 
    119 /** Decode N bytes from a Crockford's base32 string */
    120 pub fn decode<const N: usize>(encoded: &[u8]) -> Result<[u8; N], Base32Error<N>> {
    121     // Check decode length
    122     let output_length = decoded_len(encoded.len());
    123     if output_length != N {
    124         return Err(Base32Error::Length(output_length));
    125     }
    126 
    127     let mut decoded = vec![0u8; decoded_buf_len(encoded.len())]; // TODO use a stack allocated buffer when supported
    128     let mut invalid = false;
    129 
    130     // Encode chunks of 8 chars for 5B
    131     for (chunk, decoded) in encoded.chunks(8).zip(decoded.chunks_exact_mut(5)) {
    132         let mut buf = [0; 8];
    133 
    134         // Lookup chunk
    135         for (i, &b) in chunk.iter().enumerate() {
    136             buf[i] = CROCKFORD_INV[b as usize];
    137         }
    138 
    139         // Check chunk validity
    140         invalid |= buf.contains(&255);
    141 
    142         // Decode chunk
    143         decoded[0] = (buf[0] << 3) | (buf[1] >> 2);
    144         decoded[1] = (buf[1] << 6) | (buf[2] << 1) | (buf[3] >> 4);
    145         decoded[2] = (buf[3] << 4) | (buf[4] >> 1);
    146         decoded[3] = (buf[4] << 7) | (buf[5] << 2) | (buf[6] >> 3);
    147         decoded[4] = (buf[6] << 5) | buf[7];
    148     }
    149 
    150     if invalid {
    151         return Err(Base32Error::Format);
    152     }
    153     Ok(decoded[..N].try_into().unwrap())
    154 }
    155 
    156 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
    157 pub struct Base32<const L: usize>([u8; L]);
    158 
    159 impl<const L: usize> Base32<L> {
    160     pub fn rand() -> Self {
    161         let mut bytes = [0; L];
    162         fastrand::fill(&mut bytes);
    163         Self(bytes)
    164     }
    165 }
    166 
    167 impl<const L: usize> From<[u8; L]> for Base32<L> {
    168     fn from(array: [u8; L]) -> Self {
    169         Self(array)
    170     }
    171 }
    172 
    173 impl<const L: usize> Deref for Base32<L> {
    174     type Target = [u8; L];
    175 
    176     fn deref(&self) -> &Self::Target {
    177         &self.0
    178     }
    179 }
    180 
    181 impl<const L: usize> FromStr for Base32<L> {
    182     type Err = Base32Error<L>;
    183 
    184     fn from_str(s: &str) -> Result<Self, Self::Err> {
    185         Ok(Self(decode(s.as_bytes())?))
    186     }
    187 }
    188 
    189 impl<const L: usize> Display for Base32<L> {
    190     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    191         let mut buff = vec![0u8; encoded_buf_len(L)]; // TODO use a stack allocated buffer when supported
    192         f.write_str(encode_static(&self.0, &mut buff))
    193     }
    194 }
    195 
    196 impl<const L: usize> Serialize for Base32<L> {
    197     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    198     where
    199         S: Serializer,
    200     {
    201         serializer.serialize_str(&self.to_string())
    202     }
    203 }
    204 
    205 impl<'de, const L: usize> Deserialize<'de> for Base32<L> {
    206     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    207     where
    208         D: Deserializer<'de>,
    209     {
    210         Base32::from_str(&String::deserialize(deserializer)?).map_err(D::Error::custom)
    211     }
    212 }
    213 
    214 impl<'a, const L: usize> TryFrom<&'a [u8]> for Base32<L> {
    215     type Error = Base32Error<L>;
    216 
    217     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
    218         Ok(Self(
    219             value
    220                 .try_into()
    221                 .map_err(|_| Base32Error::Length(value.len()))?,
    222         ))
    223     }
    224 }