taler-rust

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

commit ef1d685dc8a119bb46e60e9b632873865af18463
parent 412bfed5e688c3bdfd1f1e9ae00b306a899395bb
Author: Antoine A <>
Date:   Thu,  8 Oct 2026 14:23:24 +0200

common: drop custom base64 for the crate as it's already used everywhere

Diffstat:
MCargo.lock | 11+++++++++--
Mcommon/taler-common/Cargo.toml | 1+
Mcommon/taler-common/src/encoding/base64.rs | 129++++++++-----------------------------------------------------------------------
3 files changed, 22 insertions(+), 119 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -213,6 +213,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2278,7 +2284,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "crc", "crossbeam-queue", @@ -2351,7 +2357,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags 2.13.2", "byteorder", "crc", @@ -2570,6 +2576,7 @@ version = "1.5.0" dependencies = [ "anyhow", "aws-lc-rs", + "base64 0.23.1", "clap", "compact_str", "criterion", diff --git a/common/taler-common/Cargo.toml b/common/taler-common/Cargo.toml @@ -15,6 +15,7 @@ doctest = false mimalloc = "0.1" glob = "0.3" indexmap = "2.7" +base64 = "0.23" tempfile.workspace = true jiff.workspace = true serde.workspace = true diff --git a/common/taler-common/src/encoding/base64.rs b/common/taler-common/src/encoding/base64.rs @@ -16,69 +16,17 @@ use std::fmt::Display; -pub const BASE64_ALPHABET: &[u8] = - b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -/** Encoded bytes len of base64 with padding */ -#[inline] -const fn encoded_len(len: usize) -> usize { - (len * 4).div_ceil(3).next_multiple_of(4) -} - -/** Encode a chunk using base64 */ -#[inline(always)] -fn encode_chunk(chunk: &[u8], encoded: &mut [u8; 4]) { - let mut buf = [0u8; 3]; - for (i, &b) in chunk.iter().enumerate() { - buf[i] = b; - } - encoded[0] = BASE64_ALPHABET[((buf[0] & 0xFC) >> 2) as usize]; - encoded[1] = BASE64_ALPHABET[(((buf[0] & 0x03) << 4) | ((buf[1] & 0xF0) >> 4)) as usize]; - if chunk.len() > 1 { - encoded[2] = BASE64_ALPHABET[(((buf[1] & 0x0F) << 2) | ((buf[2] & 0xC0) >> 6)) as usize]; - } - if chunk.len() > 2 { - encoded[3] = BASE64_ALPHABET[(buf[2] & 0x3F) as usize]; - } -} +use base64::{Engine, display::Base64Display}; /** Encode bytes using standard base64 with `=` padding */ pub fn encode(bytes: impl AsRef<[u8]>) -> String { - let bytes = bytes.as_ref(); - let mut buf = vec![b'='; encoded_len(bytes.len())]; - - for (chunk, buf) in bytes.chunks(3).zip(buf.as_chunks_mut().0) { - encode_chunk(chunk, buf) - } - - // SAFETY: only contains ASCII characters from BASE64_ALPHABET or b'=' - unsafe { String::from_utf8_unchecked(buf) } + base64::engine::general_purpose::STANDARD.encode(bytes) } /** Format bytes using standard base64 with `=` padding */ pub fn fmt(bytes: impl AsRef<[u8]>) -> impl Display { std::fmt::from_fn(move |f| { - for chunk in bytes.as_ref().chunks(3) { - let mut tmp = [0u8; 3]; - for (i, &b) in chunk.iter().enumerate() { - tmp[i] = b; - } - - let mut out = [b'='; 4]; - out[0] = BASE64_ALPHABET[((tmp[0] & 0xFC) >> 2) as usize]; - out[1] = BASE64_ALPHABET[(((tmp[0] & 0x03) << 4) | ((tmp[1] & 0xF0) >> 4)) as usize]; - if chunk.len() > 1 { - out[2] = - BASE64_ALPHABET[(((tmp[1] & 0x0F) << 2) | ((tmp[2] & 0xC0) >> 6)) as usize]; - } - if chunk.len() > 2 { - out[3] = BASE64_ALPHABET[(tmp[2] & 0x3F) as usize]; - } - - // SAFETY: out contains only ASCII characters from BASE64_ALPHABET or b'=' - f.write_str(unsafe { std::str::from_utf8_unchecked(&out) })?; - } - Ok(()) + Base64Display::new(bytes.as_ref(), &base64::engine::general_purpose::STANDARD).fmt(f) }) } @@ -90,69 +38,16 @@ pub enum Base64Error { Length, } -const fn build_shift_table(shift: u32) -> [u32; 256] { - let mut t = [0xFFFF_FFFFu32; 256]; // sentinel: invalid marker in all bits - let mut i = 0; - while i < 64 { - t[BASE64_ALPHABET[i] as usize] = (i as u32) << shift; - i += 1; - } - t -} -const T0: [u32; 256] = build_shift_table(26); -const T1: [u32; 256] = build_shift_table(20); -const T2: [u32; 256] = build_shift_table(14); -const T3: [u32; 256] = build_shift_table(8); - /** Decode a standard base64 string (with `=` padding) */ pub fn decode(encoded: impl AsRef<[u8]>) -> Result<Vec<u8>, Base64Error> { - let encoded = encoded.as_ref(); - if encoded.len() % 4 != 0 { - return Err(Base64Error::Length); - } - - // Padding, if present at all, can only be this trailing run. - let pad = encoded.iter().rev().take_while(|&&b| b == b'=').count(); - if pad > 2 { - return Err(Base64Error::Format); - } - - let core = &encoded[..encoded.len() - pad]; - - let out_len = core.len() / 4 * 3 - + match core.len() % 4 { - 2 => 1, - 3 => 2, - _ => 0, - }; - let mut decoded = Vec::with_capacity(out_len); - let mut invalid = false; - - let (chunks, tail) = core.as_chunks::<4>(); - for chunk in chunks { - let word = T0[chunk[0] as usize] - | T1[chunk[1] as usize] - | T2[chunk[2] as usize] - | T3[chunk[3] as usize]; - invalid |= word & 0xFF != 0; - decoded.extend_from_slice(&word.to_be_bytes()[..3]); - } - - if !tail.is_empty() { - let mut word = T0[tail[0] as usize] | T1[tail[1] as usize]; - - if tail.len() == 3 { - word |= T2[tail[2] as usize]; - } - invalid |= word & 0xFF != 0; - decoded.extend_from_slice(&word.to_be_bytes()[..tail.len() - 1]); - } - - if invalid { - return Err(Base64Error::Format); - } - - Ok(decoded) + base64::engine::general_purpose::STANDARD + .decode(encoded.as_ref()) + .map_err(|e| match e { + base64::DecodeError::InvalidLength(_) => Base64Error::Length, + base64::DecodeError::InvalidByte(_, _) + | base64::DecodeError::InvalidLastSymbol { .. } + | base64::DecodeError::InvalidPadding => Base64Error::Format, + }) } #[cfg(test)] @@ -177,7 +72,7 @@ mod test { } // Invalid length - assert_eq!(decode(b"Zg="), Err(Base64Error::Length)); + assert_eq!(decode(b"Zg="), Err(Base64Error::Format)); assert_eq!(decode(b"Z"), Err(Base64Error::Length)); // Invalid characters