From 8374d525299582f7988dd33e0d198af697d99ab8 Mon Sep 17 00:00:00 2001 From: mriise Date: Fri, 21 Nov 2025 12:01:29 -0800 Subject: [PATCH 1/3] refactor: use stable array chunks api. default disallow unsafe. add unsafe feature flag --- Cargo.toml | 3 ++- README.md | 2 +- src/decode.rs | 15 +++++---------- src/encode.rs | 26 ++++++++++++-------------- src/lib.rs | 2 -- 5 files changed, 20 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 92e927e..70fd2cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,5 +22,6 @@ name = "bench" harness = false [features] -#default = ["array_chunks"] +# depcrecated, will be removed in a future release array_chunks = [] +unsafe = [] diff --git a/README.md b/README.md index a1531da..691b037 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![API](https://docs.rs/base45/badge.svg)](https://docs.rs/base45) A encoder/decoder for base45 that is fully compatible with -[draft-faltstrom-base45-02](https://www.ietf.org/id/draft-faltstrom-base45-02.txt). When encoding QR or Aztec codes a different scheme than the standard base64, base32, and base16 is needed. +[rfc9285](https://datatracker.ietf.org/doc/html/rfc9285). When encoding QR or Aztec codes, a scheme other than the standard base64, base32, and base16 is needed. ## Installation diff --git a/src/decode.rs b/src/decode.rs index 5487d88..8706f9f 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -32,8 +32,11 @@ fn decode_intl(input: &[u8]) -> Result, DecodeError> { let mut output = Vec::with_capacity(match input.len() & 1 { 0 => input.len() * 2 / 3, 1 => 1 + input.len() * 2 / 3, + #[cfg(feature = "unsafe")] // SAFETY: it's one of 0 or 1. There are no other options. _ => unsafe { core::hint::unreachable_unchecked() }, + #[cfg(not(feature = "unsafe"))] + _ => unreachable!() }); #[inline(always)] @@ -58,17 +61,9 @@ fn decode_intl(input: &[u8]) -> Result, DecodeError> { alphabet::decode(v).ok_or(DecodeError) } - #[cfg(feature = "array_chunks")] - let (chunks, pre) = (input.array_chunks(), |&b| preproc(b)); - #[cfg(not(feature = "array_chunks"))] - let (chunks, pre) = (input.chunks_exact(3), |slic: &[u8]| match slic { - &[a, b, c] => preproc([a, b, c]), - // SAFETY: chunks_exact ensures every `.next()` returns an effective &[T] where `.len` == 3 - _ => unsafe { core::hint::unreachable_unchecked() }, - }); + let ((chunks, remainder), pre) = (input.as_chunks::<3>(), |&b| preproc(b)); - let remainder = chunks.remainder(); - for c in chunks.map(pre) { + for c in chunks.iter().map(pre) { let xy = core_fn(c?)?; output.extend_from_slice(&xy); } diff --git a/src/encode.rs b/src/encode.rs index ddd15c0..949aaf4 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -1,25 +1,25 @@ use crate::alphabet::{self, SIZE, SIZE_SIZE}; + +#[inline(always)] fn divmod(x: u32) -> (u32, u32) { (x / N, x % N) } +#[inline(always)] fn ae(b: u8) -> u8 { match alphabet::encode(b) { Some(ch) => ch, + #[cfg(feature = "unsafe")] // SAFETY: encode for this is highly unlikely to ever reach this point. - #[cfg(not(test))] None => unsafe { core::hint::unreachable_unchecked() }, - #[cfg(test)] + #[cfg(not(feature = "unsafe"))] None => unreachable!(), } } fn encode_buffer(input: &[u8]) -> String { // setup - #[cfg(feature = "array_chunks")] - let input = input.array_chunks::<2>(); - #[cfg(not(feature = "array_chunks"))] - let input = input.chunks_exact(2); + let (input, remainder) = input.as_chunks::<2>(); let mut s = Vec::with_capacity(input.len() + ((input.len() + 1) / 2)); @@ -33,23 +33,21 @@ fn encode_buffer(input: &[u8]) -> String { s.extend_from_slice(&[ae(c as _), ae(d as _), ae(e as _)]); } // take remainder AoT - let rem = input.remainder(); for c in input { - #[cfg(feature = "array_chunks")] - let c = *c; - // SAFETY: `chunks_exact` always returns exactly that number of items. - #[cfg(not(feature = "array_chunks"))] - let c = unsafe { [*c.get_unchecked(0), *c.get_unchecked(1)] }; - core_fn(c, &mut s); + + core_fn(*c, &mut s); } // Final byte - if let &[_0] = rem { + if let &[_0] = remainder { let (d, c) = divmod::(_0 as u32); s.extend_from_slice(&[ae(c as _), ae(d as _)]); } + #[cfg(feature = "unsafe")] // SAFETY: we control all bytes that enter this vector. unsafe { String::from_utf8_unchecked(s) } + #[cfg(not(feature = "unsafe"))] + String::from_utf8(s).expect("All bytes encoded must be ascii") } /// Encode a string to base45 diff --git a/src/lib.rs b/src/lib.rs index ff7bf7d..bd321ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#![cfg_attr(feature = "array_chunks", feature(array_chunks))] -#![cfg_attr(test, feature(test))] //! Encoder/decoder for base45 that is fully compatible with //! [`draft-faltstrom-base45-07.txt`](https://www.ietf.org/archive/id/draft-faltstrom-base45-07.txt) //! From 4fab5c2b077e1735512aeabc3c4c5675c33a9393 Mon Sep 17 00:00:00 2001 From: Rickard Natt och Dag Date: Tue, 16 Dec 2025 22:36:49 +0100 Subject: [PATCH 2/3] chore: fix lint error --- src/encode.rs | 7 ++++--- src/tests.rs | 31 ------------------------------- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/src/encode.rs b/src/encode.rs index 949aaf4..0d2f31a 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -21,7 +21,7 @@ fn encode_buffer(input: &[u8]) -> String { // setup let (input, remainder) = input.as_chunks::<2>(); - let mut s = Vec::with_capacity(input.len() + ((input.len() + 1) / 2)); + let mut s = Vec::with_capacity(input.len() + input.len().div_ceil(2)); // Core function #[inline(always)] @@ -34,7 +34,6 @@ fn encode_buffer(input: &[u8]) -> String { } // take remainder AoT for c in input { - core_fn(*c, &mut s); } // Final byte @@ -45,7 +44,9 @@ fn encode_buffer(input: &[u8]) -> String { } #[cfg(feature = "unsafe")] // SAFETY: we control all bytes that enter this vector. - unsafe { String::from_utf8_unchecked(s) } + unsafe { + String::from_utf8_unchecked(s) + } #[cfg(not(feature = "unsafe"))] String::from_utf8(s).expect("All bytes encoded must be ascii") } diff --git a/src/tests.rs b/src/tests.rs index 5ac0898..35de6aa 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -111,34 +111,3 @@ fn decode_full_bytes() { let s = decode("FGWFGWFGWFGW").unwrap(); assert_eq!(s, b"\xff\xff\xff\xff\xff\xff\xff\xff"); } - -// cursed code, quickly becomes degenerate. - -// fn rbd(bench: &mut test::Bencher) { -// use rand::{distributions::*, *}; -// use std::convert::TryFrom; -// let mut rng = thread_rng(); -// let sample = Slice::new(&crate::alphabet::TABLE).unwrap(); -// let b: Vec = sample.sample_iter(rng).copied().take(N).collect(); -// let b: [u8; N] = TryFrom::try_from(b).unwrap(); -// bench.iter(|| { -// let decoded = decode(&b[..]); -// assert!(decoded.is_ok()); -// }); -// } -// #[bench] -// fn bench_decode_random_3(b: &mut test::Bencher) { -// rbd::<3>(b); -// } -// #[bench] -// fn bench_decode_random_30(b: &mut test::Bencher) { -// rbd::<30>(b); -// } -// #[bench] -// fn bench_decode_random_3000(b: &mut test::Bencher) { -// rbd::<3000>(b); -// } -// #[bench] -// fn bench_decode_random_3002(b: &mut test::Bencher) { -// rbd::<3002>(b); -// } From aaf94d9c276543b977d8e0d414dd3619460a259c Mon Sep 17 00:00:00 2001 From: Rickard Natt och Dag Date: Tue, 16 Dec 2025 22:37:16 +0100 Subject: [PATCH 3/3] chore: formatting --- src/decode.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decode.rs b/src/decode.rs index 8706f9f..07c3361 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -36,7 +36,7 @@ fn decode_intl(input: &[u8]) -> Result, DecodeError> { // SAFETY: it's one of 0 or 1. There are no other options. _ => unsafe { core::hint::unreachable_unchecked() }, #[cfg(not(feature = "unsafe"))] - _ => unreachable!() + _ => unreachable!(), }); #[inline(always)]