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 }