commit 12bfddf05e550922e56122eb46891e6dea54eef4
parent 12a6748ad61ff09add4303341438780d015413c5
Author: Antoine A <>
Date: Sun, 5 Jan 2025 19:35:29 +0100
taler-common: faster and safer base32
Diffstat:
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))
}
}