taler-rust

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

base32.rs (8669B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024, 2025, 2026 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::{borrow::Cow, 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 /** Encode bytes using Crockford's base32 */
     48 pub fn encode(bytes: &[u8]) -> String {
     49     let mut buf = vec![0u8; encoded_buf_len(bytes.len())];
     50     // Batch encoded
     51     encode_batch(bytes, &mut buf);
     52 
     53     // Truncate incomplete ending chunk
     54     buf.truncate(encoded_len(bytes.len()));
     55 
     56     // SAFETY: only contains valid ASCII characters from CROCKFORD_ALPHABET
     57     unsafe { std::string::String::from_utf8_unchecked(buf) }
     58 }
     59 
     60 /** Batch encode bytes using Crockford's base32 */
     61 #[inline]
     62 fn encode_batch(bytes: &[u8], encoded: &mut [u8]) {
     63     // Check buffer len
     64     assert!(encoded.len() >= encoded_buf_len(bytes.len()));
     65 
     66     // Encode chunks of 5B for 8 chars
     67     for (chunk, encoded) in bytes.chunks(5).zip(encoded.chunks_exact_mut(8)) {
     68         let mut buf = [0u8; 5];
     69         for (i, &b) in chunk.iter().enumerate() {
     70             buf[i] = b;
     71         }
     72         encoded[0] = CROCKFORD_ALPHABET[((buf[0] & 0xF8) >> 3) as usize];
     73         encoded[1] = CROCKFORD_ALPHABET[(((buf[0] & 0x07) << 2) | ((buf[1] & 0xC0) >> 6)) as usize];
     74         encoded[2] = CROCKFORD_ALPHABET[((buf[1] & 0x3E) >> 1) as usize];
     75         encoded[3] = CROCKFORD_ALPHABET[(((buf[1] & 0x01) << 4) | ((buf[2] & 0xF0) >> 4)) as usize];
     76         encoded[4] = CROCKFORD_ALPHABET[(((buf[2] & 0x0F) << 1) | (buf[3] >> 7)) as usize];
     77         encoded[5] = CROCKFORD_ALPHABET[((buf[3] & 0x7C) >> 2) as usize];
     78         encoded[6] = CROCKFORD_ALPHABET[(((buf[3] & 0x03) << 3) | ((buf[4] & 0xE0) >> 5)) as usize];
     79         encoded[7] = CROCKFORD_ALPHABET[(buf[4] & 0x1F) as usize];
     80     }
     81 }
     82 
     83 #[derive(Debug, thiserror::Error)]
     84 pub enum Base32Error<const N: usize> {
     85     #[error("invalid Crockford's base32 format")]
     86     Format,
     87     #[error("invalid length expected {N} bytes got {0}")]
     88     Length(usize),
     89 }
     90 
     91 /** Crockford's base32 inverse table, case insentitive and with substitution */
     92 const CROCKFORD_INV: [u8; 256] = {
     93     let mut table = [255; 256];
     94 
     95     // Fill the canonical alphabet
     96     let mut i = 0;
     97     while i < CROCKFORD_ALPHABET.len() {
     98         let b = CROCKFORD_ALPHABET[i];
     99         table[b as usize] = i as u8;
    100         i += 1;
    101     }
    102 
    103     // Add substitution
    104     table[b'O' as usize] = table[b'0' as usize];
    105     table[b'I' as usize] = table[b'1' as usize];
    106     table[b'L' as usize] = table[b'1' as usize];
    107     table[b'U' as usize] = table[b'V' as usize];
    108 
    109     // Make the table case insensitive
    110     let mut i = 0;
    111     while i < CROCKFORD_ALPHABET.len() {
    112         let b = CROCKFORD_ALPHABET[i];
    113         table[b.to_ascii_lowercase() as usize] = table[b as usize];
    114         i += 1;
    115     }
    116 
    117     table
    118 };
    119 
    120 /** Decoded bytes len of Crockford's base32 */
    121 #[inline]
    122 const fn decoded_len(len: usize) -> usize {
    123     len * 5 / 8
    124 }
    125 
    126 /** Buffer bytes len of Crockford's base32 using a batch of 5 bytes */
    127 #[inline]
    128 const fn decoded_buf_len(len: usize) -> usize {
    129     (len / 8 + 1) * 5
    130 }
    131 
    132 /** Decode N bytes from a Crockford's base32 string */
    133 pub fn decode_static<const N: usize>(encoded: &[u8]) -> Result<[u8; N], Base32Error<N>> {
    134     // Check decode length
    135     let output_length = decoded_len(encoded.len());
    136     if output_length != N {
    137         return Err(Base32Error::Length(output_length));
    138     }
    139 
    140     let mut decoded = vec![0u8; decoded_buf_len(encoded.len())]; // TODO use a stack allocated buffer when supported
    141 
    142     if !decode_batch(encoded, &mut decoded) {
    143         return Err(Base32Error::Format);
    144     }
    145     Ok(decoded[..N].try_into().unwrap())
    146 }
    147 
    148 /** Decode bytes from a Crockford's base32 string */
    149 pub fn decode(encoded: &[u8]) -> Result<Vec<u8>, Base32Error<0>> {
    150     let mut decoded = vec![0u8; decoded_buf_len(encoded.len())];
    151 
    152     if !decode_batch(encoded, &mut decoded) {
    153         return Err(Base32Error::Format);
    154     }
    155     Ok(decoded)
    156 }
    157 
    158 /** Batch decode bytes using Crockford's base32 */
    159 #[inline]
    160 fn decode_batch(encoded: &[u8], decoded: &mut [u8]) -> bool {
    161     let mut invalid = false;
    162 
    163     // Encode chunks of 8 chars for 5B
    164     for (chunk, decoded) in encoded.chunks(8).zip(decoded.chunks_exact_mut(5)) {
    165         let mut buf = [0; 8];
    166 
    167         // Lookup chunk
    168         for (i, &b) in chunk.iter().enumerate() {
    169             buf[i] = CROCKFORD_INV[b as usize];
    170         }
    171 
    172         // Check chunk validity
    173         invalid |= buf.contains(&255);
    174 
    175         // Decode chunk
    176         decoded[0] = (buf[0] << 3) | (buf[1] >> 2);
    177         decoded[1] = (buf[1] << 6) | (buf[2] << 1) | (buf[3] >> 4);
    178         decoded[2] = (buf[3] << 4) | (buf[4] >> 1);
    179         decoded[3] = (buf[4] << 7) | (buf[5] << 2) | (buf[6] >> 3);
    180         decoded[4] = (buf[6] << 5) | buf[7];
    181     }
    182 
    183     !invalid
    184 }
    185 
    186 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
    187 pub struct Base32<const L: usize>([u8; L]);
    188 
    189 impl<const L: usize> Base32<L> {
    190     pub fn rand() -> Self {
    191         let mut bytes = [0; L];
    192         fastrand::fill(&mut bytes);
    193         Self(bytes)
    194     }
    195 }
    196 
    197 impl<const L: usize> From<[u8; L]> for Base32<L> {
    198     fn from(array: [u8; L]) -> Self {
    199         Self(array)
    200     }
    201 }
    202 
    203 impl<const L: usize> Deref for Base32<L> {
    204     type Target = [u8; L];
    205 
    206     fn deref(&self) -> &Self::Target {
    207         &self.0
    208     }
    209 }
    210 
    211 impl<const L: usize> FromStr for Base32<L> {
    212     type Err = Base32Error<L>;
    213 
    214     fn from_str(s: &str) -> Result<Self, Self::Err> {
    215         Ok(Self(decode_static(s.as_bytes())?))
    216     }
    217 }
    218 
    219 impl<const L: usize> Display for Base32<L> {
    220     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    221         let mut buff = vec![0u8; encoded_buf_len(L)]; // TODO use a stack allocated buffer when supported
    222         f.write_str(encode_static(&self.0, &mut buff))
    223     }
    224 }
    225 
    226 impl<const L: usize> Serialize for Base32<L> {
    227     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    228     where
    229         S: Serializer,
    230     {
    231         serializer.serialize_str(&self.to_string())
    232     }
    233 }
    234 
    235 impl<'de, const L: usize> Deserialize<'de> for Base32<L> {
    236     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    237     where
    238         D: Deserializer<'de>,
    239     {
    240         let raw = Cow::<str>::deserialize(deserializer)?;
    241         Self::from_str(&raw).map_err(D::Error::custom)
    242     }
    243 }
    244 
    245 impl<'a, const L: usize> TryFrom<&'a [u8]> for Base32<L> {
    246     type Error = Base32Error<L>;
    247 
    248     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
    249         Ok(Self(
    250             value
    251                 .try_into()
    252                 .map_err(|_| Base32Error::Length(value.len()))?,
    253         ))
    254     }
    255 }
    256 
    257 impl<const L: usize> sqlx::Type<sqlx::Postgres> for Base32<L> {
    258     fn type_info() -> sqlx::postgres::PgTypeInfo {
    259         <&[u8]>::type_info()
    260     }
    261 }
    262 
    263 impl<'q, const L: usize> sqlx::Encode<'q, sqlx::Postgres> for Base32<L> {
    264     fn encode_by_ref(
    265         &self,
    266         buf: &mut sqlx::postgres::PgArgumentBuffer,
    267     ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
    268         self.0.encode_by_ref(buf)
    269     }
    270 }
    271 
    272 impl<'r, const L: usize> sqlx::Decode<'r, sqlx::Postgres> for Base32<L> {
    273     fn decode(value: sqlx::postgres::PgValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
    274         let array = <[u8; L]>::decode(value)?;
    275         Ok(Self(array))
    276     }
    277 }