commit d555c5d5185aa88beb2c3be5460d030ef474028b
parent 71b444262a04ea7a362bb0da60edf5c68f90110e
Author: Antoine A <>
Date: Sat, 7 Mar 2026 19:47:13 +0100
common: improve base32 utils
Diffstat:
4 files changed, 42 insertions(+), 11 deletions(-)
diff --git a/common/taler-common/benches/base32.rs b/common/taler-common/benches/base32.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2024-2025 Taler Systems SA
+ Copyright (C) 2024, 2025, 2026 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -15,7 +15,7 @@
*/
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
-use taler_common::types::base32::{Base32, decode, encode_static};
+use taler_common::types::base32::{Base32, decode_static, encode_static};
fn parser(c: &mut Criterion) {
let mut buf = [0u8; 255];
@@ -35,14 +35,14 @@ fn parser(c: &mut Criterion) {
c.bench_function("base32_decode_valid", |b| {
b.iter_batched(
|| Base32::<64>::rand().to_string(),
- |case| decode::<64>(case.as_bytes()).unwrap(),
+ |case| decode_static::<64>(case.as_bytes()).unwrap(),
BatchSize::SmallInput,
)
});
c.bench_function("base32_decode_random", |b| {
b.iter_batched(
|| (0..56).map(|_| fastrand::char(..)).collect::<String>(),
- |case| decode::<64>(case.as_bytes()).ok(),
+ |case| decode_static::<64>(case.as_bytes()).ok(),
BatchSize::SmallInput,
)
});
diff --git a/common/taler-common/src/log.rs b/common/taler-common/src/log.rs
@@ -72,6 +72,7 @@ pub fn taler_logger(max_level: Option<Level>) -> impl SubscriberInitExt {
*metadata.level() <= max_level
&& !(target.starts_with("sqlx")
|| target.contains("hyper_util")
+ || target.starts_with("h2")
|| target.starts_with("reqwest")
|| target.starts_with("rustls")
|| target.starts_with("hyper_rustls"))
diff --git a/common/taler-common/src/types/base32.rs b/common/taler-common/src/types/base32.rs
@@ -44,6 +44,19 @@ pub fn encode_static<'a, const N: usize>(bytes: &[u8; N], out: &'a mut [u8]) ->
unsafe { std::str::from_utf8_unchecked(truncated) }
}
+/** Encode bytes using Crockford's base32 */
+pub fn encode(bytes: &[u8]) -> String {
+ let mut buf = vec![0u8; encoded_buf_len(bytes.len())];
+ // Batch encoded
+ encode_batch(bytes, &mut buf);
+
+ // Truncate incomplete ending chunk
+ buf.truncate(encoded_len(bytes.len()));
+
+ // SAFETY: only contains valid ASCII characters from CROCKFORD_ALPHABET
+ unsafe { std::string::String::from_utf8_unchecked(buf) }
+}
+
/** Batch encode bytes using Crockford's base32 */
#[inline]
fn encode_batch(bytes: &[u8], encoded: &mut [u8]) {
@@ -117,7 +130,7 @@ const fn decoded_buf_len(len: usize) -> usize {
}
/** Decode N bytes from a Crockford's base32 string */
-pub fn decode<const N: usize>(encoded: &[u8]) -> Result<[u8; N], Base32Error<N>> {
+pub fn decode_static<const N: usize>(encoded: &[u8]) -> Result<[u8; N], Base32Error<N>> {
// Check decode length
let output_length = decoded_len(encoded.len());
if output_length != N {
@@ -125,6 +138,26 @@ pub fn decode<const N: usize>(encoded: &[u8]) -> Result<[u8; N], Base32Error<N>>
}
let mut decoded = vec![0u8; decoded_buf_len(encoded.len())]; // TODO use a stack allocated buffer when supported
+
+ if !decode_batch(encoded, &mut decoded) {
+ return Err(Base32Error::Format);
+ }
+ Ok(decoded[..N].try_into().unwrap())
+}
+
+/** Decode bytes from a Crockford's base32 string */
+pub fn decode(encoded: &[u8]) -> Result<Vec<u8>, Base32Error<0>> {
+ let mut decoded = vec![0u8; decoded_buf_len(encoded.len())];
+
+ if !decode_batch(encoded, &mut decoded) {
+ return Err(Base32Error::Format);
+ }
+ Ok(decoded)
+}
+
+/** Batch decode bytes using Crockford's base32 */
+#[inline]
+fn decode_batch(encoded: &[u8], decoded: &mut [u8]) -> bool {
let mut invalid = false;
// Encode chunks of 8 chars for 5B
@@ -147,10 +180,7 @@ pub fn decode<const N: usize>(encoded: &[u8]) -> Result<[u8; N], Base32Error<N>>
decoded[4] = (buf[6] << 5) | buf[7];
}
- if invalid {
- return Err(Base32Error::Format);
- }
- Ok(decoded[..N].try_into().unwrap())
+ !invalid
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
@@ -182,7 +212,7 @@ impl<const L: usize> FromStr for Base32<L> {
type Err = Base32Error<L>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
- Ok(Self(decode(s.as_bytes())?))
+ Ok(Self(decode_static(s.as_bytes())?))
}
}
diff --git a/taler-magnet-bank/Cargo.toml b/taler-magnet-bank/Cargo.toml
@@ -12,7 +12,7 @@ license-file.workspace = true
form_urlencoded = "1.2"
percent-encoding = "2.3"
rpassword = "7.4"
-getrandom = "0.3.4"
+getrandom = "0.4.1"
sqlx.workspace = true
serde_json = { workspace = true, features = ["raw_value"] }
jiff = { workspace = true, features = ["serde"] }