taler-rust

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

commit f5e3bb2d417428ccba9a23e3556d7ad11a0b3d4a
parent 06dd73e56e2893fabdb68e5f4d91bce8a647e453
Author: Antoine A <>
Date:   Thu,  3 Sep 2026 12:12:48 +0200

common: improve code and add more benchmarks

Diffstat:
Madapters/taler-cyclos/src/worker.rs | 9++++-----
Madapters/taler-magnet-bank/src/setup.rs | 4++--
Madapters/taler-magnet-bank/src/worker.rs | 4++--
Mcommon/http-client/src/lib.rs | 7+++++--
Mcommon/taler-api/src/db.rs | 4++--
Mcommon/taler-common/Cargo.toml | 2+-
Dcommon/taler-common/benches/base32.rs | 57---------------------------------------------------------
Acommon/taler-common/benches/encoding.rs | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcommon/taler-common/src/api/params.rs | 2+-
Mcommon/taler-common/src/encoding/base32.rs | 14+-------------
10 files changed, 174 insertions(+), 85 deletions(-)

diff --git a/adapters/taler-cyclos/src/worker.rs b/adapters/taler-cyclos/src/worker.rs @@ -149,10 +149,9 @@ pub async fn run_worker( tokio::time::sleep(Duration::from_secs(15)).await; skip_notifications = false; } - WorkerError::Api(ApiErr { - ctx: _, - err: CyclosErr::Input(InputError::Validation { .. }), - }) => { + WorkerError::Api(ApiErr { ctx: _, err }) + if matches!(*err, CyclosErr::Input(InputError::Validation { .. })) => + { // In case of validation failure we do not want to retry right away as it can DOS the service skip_notifications = false; } @@ -245,7 +244,7 @@ impl Worker<'_> { trace!(target: "worker", "init tx {}", tx.id); } Err(e) => { - let msg = match e.err { + let msg = match &*e.err { CyclosErr::Unknown(NotFoundError { entity_type, key }) => { format!("unknown {entity_type} {key}") } diff --git a/adapters/taler-magnet-bank/src/setup.rs b/adapters/taler-magnet-bank/src/setup.rs @@ -125,7 +125,7 @@ pub async fn setup(cfg: WorkerCfg, reset: bool) -> anyhow::Result<()> { let sca_code = rpassword::prompt_password("Enter the code>")?; if let Err(e) = client.perform_sca(&sca_code).await { // Ignore error if SCA already performed - if !matches!(e.err, MagnetErr::Magnet(MagnetError { ref short_message, .. }) if short_message == "TOKEN_SCA_HITELESITETT") + if !matches!(&*e.err, MagnetErr::Magnet(MagnetError { short_message, .. }) if short_message == "TOKEN_SCA_HITELESITETT") { return Err(e.into()); } @@ -144,7 +144,7 @@ pub async fn setup(cfg: WorkerCfg, reset: bool) -> anyhow::Result<()> { }; if let Err(e) = client.upload_public_key(&signing_key).await { // Ignore error if public key already uploaded - if !matches!(e.err, MagnetErr::Magnet(MagnetError { ref short_message, .. }) if short_message== "KULCS_MAR_HASZNALATBAN") + if !matches!(&*e.err, MagnetErr::Magnet(MagnetError { short_message, .. }) if short_message == "KULCS_MAR_HASZNALATBAN") { return Err(e.into()); } diff --git a/adapters/taler-magnet-bank/src/worker.rs b/adapters/taler-magnet-bank/src/worker.rs @@ -515,7 +515,7 @@ impl Worker<'_> { info } Err(e) => { - if let MagnetErr::Magnet(e) = &e.err { + if let MagnetErr::Magnet(e) = &*e.err { // Check if error is permanent if matches!( (e.error_code, e.short_message.as_str()), @@ -569,7 +569,7 @@ impl Worker<'_> { { Ok(_) => Ok(()), Err(e) => { - if let MagnetErr::Magnet(e) = &e.err { + if let MagnetErr::Magnet(e) = &*e.err { // Check if soft failure if matches!( (e.error_code, e.short_message.as_str()), diff --git a/common/http-client/src/lib.rs b/common/http-client/src/lib.rs @@ -92,7 +92,10 @@ pub struct Ctx { impl Ctx { pub fn wrap<E: std::error::Error>(self, err: E) -> ApiErr<E> { - ApiErr { ctx: self, err } + ApiErr { + ctx: self, + err: Box::new(err), + } } } @@ -116,5 +119,5 @@ impl Display for Ctx { #[error("{ctx} {err}")] pub struct ApiErr<E: std::error::Error> { pub ctx: Ctx, - pub err: E, + pub err: Box<E>, } diff --git a/common/taler-api/src/db.rs b/common/taler-api/src/db.rs @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2024, 2025, 2026 Taler Systems SA + Copyright (C) 2024-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 @@ -131,7 +131,7 @@ pub async fn page<'a, 'b, R: Send + Unpin>( if params.backward() { "DESC" } else { "ASC" } )); builder - .push_bind(params.len()) + .push_bind(params.limit()) .build() .try_map(map) .fetch_all(db) diff --git a/common/taler-common/Cargo.toml b/common/taler-common/Cargo.toml @@ -40,7 +40,7 @@ taler-macros.workspace = true criterion.workspace = true [[bench]] -name = "base32" +name = "encoding" harness = false [[bench]] diff --git a/common/taler-common/benches/base32.rs b/common/taler-common/benches/base32.rs @@ -1,57 +0,0 @@ -/* - This file is part of TALER - 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 - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ - -use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; -use rand::RngExt as _; -use taler_common::{ - encoding::base32::{decode_static, encode_static}, - types::base32::Base32, -}; - -fn parser(c: &mut Criterion) { - let mut buf = [0u8; 255]; - c.bench_function("base32_encode_random", |b| { - b.iter_batched( - rand::random::<[u8; 64]>, - |case| { - encode_static(&case, &mut buf); - }, - BatchSize::SmallInput, - ) - }); - c.bench_function("base32_decode_valid", |b| { - b.iter_batched( - || Base32::<64>::rand().to_string(), - |case| decode_static::<64>(case.as_bytes()).unwrap(), - BatchSize::SmallInput, - ) - }); - c.bench_function("base32_decode_random", |b| { - b.iter_batched( - || { - rand::rng() - .sample_iter::<char, _>(&rand::distr::StandardUniform) - .take(56) - .collect::<String>() - }, - |case| decode_static::<64>(case.as_bytes()).ok(), - BatchSize::SmallInput, - ) - }); -} - -criterion_group!(benches, parser); -criterion_main!(benches); diff --git a/common/taler-common/benches/encoding.rs b/common/taler-common/benches/encoding.rs @@ -0,0 +1,156 @@ +/* + This file is part of TALER + 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ + +use std::hint::black_box; +use std::str::FromStr; + +use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; +use taler_common::{ + encoding::base32, + encoding::base64, + encoding::hex, + types::base32::Base32, +}; + +fn parser(c: &mut Criterion) { + macro_rules! bench32 { + ($group:expr, $size:expr) => {{ + const N: usize = $size; + $group.throughput(Throughput::Bytes(N as u64)); + + $group.bench_function(BenchmarkId::new("encode", N), |b| { + b.iter_batched( + rand::random::<[u8; N]>, + |case| black_box(base32::encode(&case)), + BatchSize::LargeInput, + ) + }); + $group.bench_function(BenchmarkId::new("fmt", N), |b| { + b.iter_batched( + Base32::<N>::rand, + |case| black_box(case.to_string()), + BatchSize::LargeInput, + ) + }); + $group.bench_function(BenchmarkId::new("decode", N), |b| { + b.iter_batched( + || Base32::<N>::rand().to_string(), + |case| black_box(base32::decode(case.as_bytes())), + BatchSize::LargeInput, + ) + }); + $group.bench_function(BenchmarkId::new("decode_static", N), |b| { + b.iter_batched( + || Base32::<N>::rand().to_string(), + |case| black_box(Base32::<N>::from_str(&case).unwrap()), + BatchSize::LargeInput, + ) + }); + }}; + } + let mut group = c.benchmark_group("base32"); + + bench32!(group, 64); + bench32!(group, 1024); + bench32!(group, 16 * 1024); + bench32!(group, 1024 * 1024); + + group.finish(); + + macro_rules! bench64 { + ($group:expr, $size:expr) => {{ + const N: usize = $size; + $group.throughput(Throughput::Bytes(N as u64)); + + $group.bench_function(BenchmarkId::new("encode", N), |b| { + b.iter_batched( + rand::random::<[u8; N]>, + |case| black_box(base64::encode(&case)), + BatchSize::LargeInput, + ) + }); + $group.bench_function(BenchmarkId::new("fmt", N), |b| { + b.iter_batched( + rand::random::<[u8; N]>, + |case| black_box(base64::fmt(case).to_string()), + BatchSize::LargeInput, + ) + }); + $group.bench_function(BenchmarkId::new("decode", N), |b| { + b.iter_batched( + || base64::encode(rand::random::<[u8; N]>()), + |case| black_box(base64::decode(case.as_bytes())), + BatchSize::LargeInput, + ) + }); + $group.bench_function(BenchmarkId::new("random", N), |b| { + b.iter_batched( + || base64::encode(rand::random::<[u8; N]>()), + |case| black_box(base64::decode(case.as_bytes())), + BatchSize::LargeInput, + ) + }); + }}; + } + let mut group = c.benchmark_group("base64"); + + bench64!(group, 64); + bench64!(group, 1024); + bench64!(group, 16 * 1024); + bench64!(group, 1024 * 1024); + + group.finish(); + + macro_rules! bench16 { + ($group:expr, $size:expr) => {{ + const N: usize = $size; + $group.throughput(Throughput::Bytes(N as u64)); + + $group.bench_function(BenchmarkId::new("encode", N), |b| { + b.iter_batched( + rand::random::<[u8; N]>, + |case| black_box(hex::encode(&case)), + BatchSize::LargeInput, + ) + }); + $group.bench_function(BenchmarkId::new("fmt", N), |b| { + b.iter_batched( + rand::random::<[u8; N]>, + |case| black_box(hex::fmt(case).to_string()), + BatchSize::LargeInput, + ) + }); + $group.bench_function(BenchmarkId::new("decode", N), |b| { + b.iter_batched( + || hex::encode(rand::random::<[u8; N]>()), + |case| black_box(hex::decode(case.as_bytes())), + BatchSize::LargeInput, + ) + }); + }}; + } + let mut group = c.benchmark_group("base16"); + + bench16!(group, 64); + bench16!(group, 1024); + bench16!(group, 16 * 1024); + bench16!(group, 1024 * 1024); + + group.finish(); +} + +criterion_group!(benches, parser); +criterion_main!(benches); diff --git a/common/taler-common/src/api/params.rs b/common/taler-common/src/api/params.rs @@ -95,7 +95,7 @@ impl Page { self.limit < 0 } - pub fn len(&self) -> i64 { + pub fn limit(&self) -> i64 { self.limit.saturating_abs() } } diff --git a/common/taler-common/src/encoding/base32.rs b/common/taler-common/src/encoding/base32.rs @@ -31,18 +31,6 @@ pub(crate) const fn encoded_buf_len(len: usize) -> usize { } /** Encode bytes using Crockford's base32 */ -pub fn encode_static<'a, const N: usize>(bytes: &[u8; N], out: &'a mut [u8]) -> &'a str { - // Batch encoded - encode_batch(bytes, out); - - // Truncate incomplete ending chunk - let truncated = &out[..encoded_len(bytes.len())]; - - // SAFETY: only contains valid ASCII characters from CROCKFORD_ALPHABET - unsafe { std::str::from_utf8_unchecked(truncated) } -} - -/** Encode bytes using Crockford's base32 */ pub fn encode(bytes: impl AsRef<[u8]>) -> String { let bytes = bytes.as_ref(); let mut buf = vec![0u8; encoded_buf_len(bytes.len())]; @@ -191,7 +179,7 @@ fn decode_batch(encoded: &[u8], decoded: &mut [u8]) -> bool { let mut invalid = false; // Encode chunks of 8 chars for 5B - for (chunk, decoded) in encoded.chunks(8).zip(decoded.chunks_exact_mut(5)) { + for (chunk, decoded) in encoded.chunks(8).zip(decoded.as_chunks_mut::<5>().0) { let mut buf = [0; 8]; // Lookup chunk