commit f5e3bb2d417428ccba9a23e3556d7ad11a0b3d4a
parent 06dd73e56e2893fabdb68e5f4d91bce8a647e453
Author: Antoine A <>
Date: Thu, 3 Sep 2026 12:12:48 +0200
common: improve code and add more benchmarks
Diffstat:
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