taler-rust

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

commit 12bfddf05e550922e56122eb46891e6dea54eef4
parent 12a6748ad61ff09add4303341438780d015413c5
Author: Antoine A <>
Date:   Sun,  5 Jan 2025 19:35:29 +0100

taler-common: faster and safer base32

Diffstat:
MCargo.lock | 136+++++++++++++++++++++++++------------------------------------------------------
Mtaler-common/benches/base32.rs | 57+++++++++++++++++++++++++--------------------------------
Mtaler-common/src/types/base32.rs | 150+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
3 files changed, 154 insertions(+), 189 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -18,18 +18,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -89,9 +77,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "1b1244b10dcd56c92219da4e14caa97e312079e185f04ba3eea25061561dc0a0" dependencies = [ "proc-macro2", "quote", @@ -281,9 +269,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.6" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" dependencies = [ "shlex", ] @@ -729,6 +717,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -857,24 +851,25 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "hashlink" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.2", ] [[package]] @@ -1236,9 +1231,9 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jiff" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943611a469f78ab9afdac9022e473a80fca16a9deca6c5be3eb566d872231e76" +checksum = "ed0ce60560149333a8e41ca7dc78799c47c5fd435e2bc18faf6a054382eec037" dependencies = [ "log", "portable-atomic", @@ -1301,7 +1296,6 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ - "cc", "pkg-config", "vcpkg", ] @@ -1393,12 +1387,6 @@ dependencies = [ ] [[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] name = "miniz_oxide" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1419,16 +1407,6 @@ dependencies = [ ] [[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1548,12 +1526,6 @@ dependencies = [ ] [[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] name = "pem-rfc7468" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2146,20 +2118,10 @@ dependencies = [ ] [[package]] -name = "sqlformat" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" -dependencies = [ - "nom", - "unicode_categories", -] - -[[package]] name = "sqlx" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" +checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2170,30 +2132,25 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" +checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0" dependencies = [ - "atoi", - "byteorder", "bytes", "crc", "crossbeam-queue", "either", "event-listener", - "futures-channel", "futures-core", "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "hashlink", - "hex", "indexmap 2.7.0", "log", "memchr", "once_cell", - "paste", "percent-encoding", "rustls", "rustls-pemfile", @@ -2201,8 +2158,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "sqlformat", - "thiserror 1.0.69", + "thiserror 2.0.9", "tokio", "tokio-stream", "tracing", @@ -2212,9 +2168,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" +checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310" dependencies = [ "proc-macro2", "quote", @@ -2225,9 +2181,9 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" +checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad" dependencies = [ "dotenvy", "either", @@ -2249,9 +2205,9 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" +checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233" dependencies = [ "atoi", "base64", @@ -2283,16 +2239,16 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 1.0.69", + "thiserror 2.0.9", "tracing", "whoami", ] [[package]] name = "sqlx-postgres" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" +checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613" dependencies = [ "atoi", "base64", @@ -2303,7 +2259,6 @@ dependencies = [ "etcetera", "futures-channel", "futures-core", - "futures-io", "futures-util", "hex", "hkdf", @@ -2321,16 +2276,16 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 1.0.69", + "thiserror 2.0.9", "tracing", "whoami", ] [[package]] name = "sqlx-sqlite" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" +checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540" dependencies = [ "atoi", "flume", @@ -2379,9 +2334,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.94" +version = "2.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" dependencies = [ "proc-macro2", "quote", @@ -2449,12 +2404,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", "windows-sys 0.59.0", @@ -2784,12 +2740,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" - -[[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/taler-common/benches/base32.rs b/taler-common/benches/base32.rs @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2024 Taler Systems SA + Copyright (C) 2025 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 @@ -14,44 +14,37 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use taler_common::types::base32::{decode, encode_static, encoded_batch_len, Base32}; +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use taler_common::types::base32::{decode, encode_static, Base32}; fn parser(c: &mut Criterion) { - let mut rng = fastrand::Rng::new(); - let encode_random: Vec<_> = (0..30) - .map(|_| { - let mut bytes = [0; 64]; - rng.fill(&mut bytes); - bytes - }) - .collect(); - let decode_valid: Vec<String> = (0..30).map(|_| Base32::<64>::rand().to_string()).collect(); - let decode_randoms: Vec<String> = (0..30) - .map(|_| (0..56).map(|_| rng.char(..)).collect()) - .collect(); - - let mut buf = [0u8; encoded_batch_len(64)]; + let mut buf = [0u8; 255]; c.bench_function("base32_encode_random", |b| { - b.iter(|| { - for case in &encode_random { - encode_static(black_box(case), &mut buf); - } - }) + b.iter_batched( + || { + let mut bytes = [0; 64]; + fastrand::fill(&mut bytes); + bytes + }, + |case| { + encode_static(&case, &mut buf); + }, + BatchSize::SmallInput, + ) }); c.bench_function("base32_decode_valid", |b| { - b.iter(|| { - for case in &decode_valid { - decode::<32>(black_box(case.as_str())).ok(); - } - }) + b.iter_batched( + || Base32::<64>::rand().to_string(), + |case| decode::<64>(case.as_bytes()).unwrap(), + BatchSize::SmallInput, + ) }); c.bench_function("base32_decode_random", |b| { - b.iter(|| { - for case in &decode_randoms { - decode::<32>(black_box(case.as_str())).ok(); - } - }) + b.iter_batched( + || (0..56).map(|_| fastrand::char(..)).collect::<String>(), + |case| decode::<64>(case.as_bytes()).ok(), + BatchSize::SmallInput, + ) }); } diff --git a/taler-common/src/types/base32.rs b/taler-common/src/types/base32.rs @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2024 Taler Systems SA + Copyright (C) 2025 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 @@ -22,18 +22,17 @@ pub const CROCKFORD_ALPHABET: &[u8] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ"; /** Encoded bytes len of Crockford's base32 */ #[inline] -pub const fn encoded_len(len: usize) -> usize { - return (len * 8 + 4) / 5; +const fn encoded_len(len: usize) -> usize { + (len * 8 + 4) / 5 } /** Buffer bytes len of Crockford's base32 using a batch of 8 chars */ #[inline] -pub const fn encoded_batch_len(len: usize) -> usize { - return (len / 5 + 1) * 8; +const fn encoded_buf_len(len: usize) -> usize { + (len / 5 + 1) * 8 } /** Encode bytes using Crockford's base32 */ -#[inline] pub fn encode_static<'a, const N: usize>(bytes: &[u8; N], out: &'a mut [u8]) -> &'a str { // Batch encoded encode_batch(bytes, out); @@ -42,31 +41,29 @@ pub fn encode_static<'a, const N: usize>(bytes: &[u8; N], out: &'a mut [u8]) -> let truncated = &out[..encoded_len(bytes.len())]; // SAFETY: only contains valid ASCII characters from CROCKFORD_ALPHABET - unsafe { std::str::from_utf8_unchecked(&truncated) } + unsafe { std::str::from_utf8_unchecked(truncated) } } /** Batch encode bytes using Crockford's base32 */ #[inline] -fn encode_batch(bytes: &[u8], out: &mut [u8]) { +fn encode_batch(bytes: &[u8], encoded: &mut [u8]) { // Check buffer len - assert!(out.len() >= encoded_batch_len(bytes.len())); + assert!(encoded.len() >= encoded_buf_len(bytes.len())); // Encode chunks of 5B for 8 chars - for (i, chunk) in bytes.chunks(5).enumerate() { + for (chunk, encoded) in bytes.chunks(5).zip(encoded.chunks_exact_mut(8)) { let mut buf = [0u8; 5]; for (i, &b) in chunk.iter().enumerate() { buf[i] = b; } - // SAFETY: we only write at most encoded_batch_len bytes - let out = unsafe { out.get_unchecked_mut(i * 8..(i + 1) * 8) }; - out[0] = CROCKFORD_ALPHABET[((buf[0] & 0xF8) >> 3) as usize]; - out[1] = CROCKFORD_ALPHABET[(((buf[0] & 0x07) << 2) | ((buf[1] & 0xC0) >> 6)) as usize]; - out[2] = CROCKFORD_ALPHABET[((buf[1] & 0x3E) >> 1) as usize]; - out[3] = CROCKFORD_ALPHABET[(((buf[1] & 0x01) << 4) | ((buf[2] & 0xF0) >> 4)) as usize]; - out[4] = CROCKFORD_ALPHABET[(((buf[2] & 0x0F) << 1) | (buf[3] >> 7)) as usize]; - out[5] = CROCKFORD_ALPHABET[((buf[3] & 0x7C) >> 2) as usize]; - out[6] = CROCKFORD_ALPHABET[(((buf[3] & 0x03) << 3) | ((buf[4] & 0xE0) >> 5)) as usize]; - out[7] = CROCKFORD_ALPHABET[(buf[4] & 0x1F) as usize]; + encoded[0] = CROCKFORD_ALPHABET[((buf[0] & 0xF8) >> 3) as usize]; + encoded[1] = CROCKFORD_ALPHABET[(((buf[0] & 0x07) << 2) | ((buf[1] & 0xC0) >> 6)) as usize]; + encoded[2] = CROCKFORD_ALPHABET[((buf[1] & 0x3E) >> 1) as usize]; + encoded[3] = CROCKFORD_ALPHABET[(((buf[1] & 0x01) << 4) | ((buf[2] & 0xF0) >> 4)) as usize]; + encoded[4] = CROCKFORD_ALPHABET[(((buf[2] & 0x0F) << 1) | (buf[3] >> 7)) as usize]; + encoded[5] = CROCKFORD_ALPHABET[((buf[3] & 0x7C) >> 2) as usize]; + encoded[6] = CROCKFORD_ALPHABET[(((buf[3] & 0x03) << 3) | ((buf[4] & 0xE0) >> 5)) as usize]; + encoded[7] = CROCKFORD_ALPHABET[(buf[4] & 0x1F) as usize]; } } @@ -78,57 +75,82 @@ pub enum Base32Error<const N: usize> { Length(usize), } -#[rustfmt::skip] -/* - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, :, ;, <, =, >, ?, @, A, B, C, - D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, - X, Y, Z, [, \, ], ^, _, `, a, b, c, d, e, f, g, h, i, j, k, - l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, -*/ -const CROCKFORD_INV: [i8; 75] = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, - 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, 22, 23, 24, 25, 26, -1, 27, 28, - 29, 30, 31, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, - 1, 20, 21, 0, 22, 23, 24, 25, 26, -1, 27, 28, 29, 30, 31, -]; +/** Crockford's base32 inverse table, case insentitive and with substitution */ +const CROCKFORD_INV: [u8; 256] = { + let mut table = [255; 256]; -/** Decode N bytes from a Crockford's base32 string */ -pub fn decode<const N: usize>(str: &str) -> Result<[u8; N], Base32Error<N>> { - let encoded = str.as_bytes(); + // Fill the canonical alphabet + let mut i = 0; + while i < CROCKFORD_ALPHABET.len() { + let b = CROCKFORD_ALPHABET[i]; + table[b as usize] = i as u8; + i += 1; + } + + // Add substitution + table[b'O' as usize] = table[b'0' as usize]; + table[b'I' as usize] = table[b'1' as usize]; + table[b'L' as usize] = table[b'1' as usize]; + table[b'U' as usize] = table[b'V' as usize]; + + // Make the table case insensitive + let mut i = 0; + while i < CROCKFORD_ALPHABET.len() { + let b = CROCKFORD_ALPHABET[i]; + table[b.to_ascii_lowercase() as usize] = table[b as usize]; + i += 1; + } + + table +}; + +/** Decoded bytes len of Crockford's base32 */ +#[inline] +const fn decoded_len(len: usize) -> usize { + len * 5 / 8 +} +/** Buffer bytes len of Crockford's base32 using a batch of 5 bytes */ +#[inline] +const fn decoded_buf_len(len: usize) -> usize { + (len / 8 + 1) * 5 +} + +/** Decode N bytes from a Crockford's base32 string */ +pub fn decode<const N: usize>(encoded: &[u8]) -> Result<[u8; N], Base32Error<N>> { // Check decode length - let output_length = encoded.len() * 5 / 8; + let output_length = decoded_len(encoded.len()); if output_length != N { return Err(Base32Error::Length(output_length)); } - let mut decoded = [0u8; N]; - let mut bits = 0; - let mut buf = 0u16; - let mut cursor = 0usize; - - for b in encoded { - // Read input - match CROCKFORD_INV.get(b.wrapping_sub(b'0') as usize).copied() { - Some(-1) | None => return Err(Base32Error::Format), - Some(lookup) => { - // Add 5 bits - buf = (buf << 5) | (lookup as u16); - bits += 5; - - // Write byte if full - if bits >= 8 { - bits -= 8; - unsafe { - // SAFETY: never produce more than L bytes - *decoded.get_unchecked_mut(cursor) = (buf >> bits) as u8; - } - cursor += 1; - } - } + let mut decoded = vec![0u8; decoded_buf_len(encoded.len())]; // TODO use a stack allocated buffer when supported + let mut invalid = false; + + // Encode chunks of 8 chars for 5B + for (chunk, decoded) in encoded.chunks(8).zip(decoded.chunks_exact_mut(5)) { + let mut buf = [0; 8]; + + // Lookup chunk + for (i, &b) in chunk.iter().enumerate() { + buf[i] = CROCKFORD_INV[b as usize]; } + + // Check chunk validity + invalid |= buf.contains(&255); + + // Decode chunk + decoded[0] = (buf[0] << 3) | (buf[1] >> 2); + decoded[1] = (buf[1] << 6) | (buf[2] << 1) | (buf[3] >> 4); + decoded[2] = (buf[3] << 4) | (buf[4] >> 1); + decoded[3] = (buf[4] << 7) | (buf[5] << 2) | (buf[6] >> 3); + decoded[4] = (buf[6] << 5) | buf[7]; + } + + if invalid { + return Err(Base32Error::Format); } - Ok(decoded) + Ok(decoded[..N].try_into().unwrap()) } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -160,13 +182,13 @@ 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)?)) + Ok(Self(decode(s.as_bytes())?)) } } impl<const L: usize> Display for Base32<L> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut buff = vec![0u8; encoded_batch_len(L)]; // TODO use a stack allocated buffer when supported + let mut buff = vec![0u8; encoded_buf_len(L)]; // TODO use a stack allocated buffer when supported f.write_str(encode_static(&self.0, &mut buff)) } }