base64url_nopad

base64url without padding library.
git clone https://git.philomathiclife.com/repos/base64url_nopad
Log | Files | Refs | README

commit c0544cf7d571e5b075953258873953a8451b2097
parent 964b610037e2a8d1104d2840bc2f61d286b29193
Author: Zack Newman <zack@philomathiclife.com>
Date:   Fri, 21 Nov 2025 22:30:54 -0700

address clippy for tests

Diffstat:
MCargo.toml | 21+++++++++++++++++----
MREADME.md | 13+++++++------
Msrc/lib.rs | 59++++++++++++++++++++++++++++++-----------------------------
3 files changed, 54 insertions(+), 39 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -10,7 +10,7 @@ name = "base64url_nopad" readme = "README.md" repository = "https://git.philomathiclife.com/repos/base64url_nopad/" rust-version = "1.89.0" -version = "0.1.1" +version = "0.1.2" [lints.rust] ambiguous_negative_literals = { level = "deny", priority = -1 } @@ -23,7 +23,7 @@ future_incompatible = { level = "deny", priority = -1 } impl_trait_redundant_captures = { level = "deny", priority = -1 } keyword_idents = { level = "deny", priority = -1 } let_underscore = { level = "deny", priority = -1 } -#linker_messages = { level = "deny", priority = -1 } +linker_messages = { level = "deny", priority = -1 } #lossy_provenance_casts = { level = "deny", priority = -1 } macro_use_extern_crate = { level = "deny", priority = -1 } meta_variable_misuse = { level = "deny", priority = -1 } @@ -44,9 +44,11 @@ rust_2021_compatibility = { level = "deny", priority = -1 } rust_2024_compatibility = { level = "deny", priority = -1 } single_use_lifetimes = { level = "deny", priority = -1 } #supertrait_item_shadowing_definition = { level = "deny", priority = -1 } +#supertrait_item_shadowing_usage = { level = "deny", priority = -1 } trivial_casts = { level = "deny", priority = -1 } trivial_numeric_casts = { level = "deny", priority = -1 } unit_bindings = { level = "deny", priority = -1 } +unknown_or_malformed_diagnostic_attributes = { level = "deny", priority = -1 } unnameable_types = { level = "deny", priority = -1 } #unqualified_local_imports = { level = "deny", priority = -1 } unreachable_pub = { level = "deny", priority = -1 } @@ -62,7 +64,6 @@ variant_size_differences = { level = "deny", priority = -1 } warnings = { level = "deny", priority = -1 } [lints.clippy] -all = { level = "deny", priority = -1 } cargo = { level = "deny", priority = -1 } complexity = { level = "deny", priority = -1 } correctness = { level = "deny", priority = -1 } @@ -87,7 +88,19 @@ unseparated_literal_suffix = "allow" [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] +default-target = "x86_64-unknown-linux-gnu" +targets = [ + "aarch64-apple-darwin", + "aarch64-pc-windows-msvc", + "aarch64-unknown-linux-gnu", + "i686-pc-windows-msvc", + "i686-unknown-linux-gnu", + "x86_64-pc-windows-gnu", + "x86_64-pc-windows-msvc", + "x86_64-unknown-freebsd", + "x86_64-unknown-linux-musl", + "x86_64-unknown-netbsd" +] [dev-dependencies] rand = { version = "0.9.2", default-features = false, features = ["os_rng", "small_rng"] } diff --git a/README.md b/README.md @@ -74,12 +74,13 @@ at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. -Before any PR is sent, `cargo clippy` and `cargo t` should be run _for each possible combination of "features"_ -using stable Rust. One easy way to achieve this is by building `ci` and invoking it with no commands in the -`base64url_nopad` directory or sub-directories. You can fetch `ci` via -`git clone https://git.philomathiclife.com/repos/ci`, and it can be built with `cargo build --release`. -Additionally, `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features` should be run to ensure -documentation can be built. +Before any PR is sent, `cargo clippy --all-targets`, `cargo test --all-targets`, and `cargo test --doc` should be +run _for each possible combination of "features"_ using the stable and MSRV toolchains. One easy way to achieve this +is by invoking [`ci-cargo`](https://crates.io/crates/ci-cargo) as `ci-cargo clippy --all-targets test --all-targets` +in the `base64url_nopad` directory. + +Last, `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features` should be run to ensure documentation can be +built. ### Status diff --git a/src/lib.rs b/src/lib.rs @@ -944,20 +944,14 @@ pub const fn encode_len_checked(input_length: usize) -> Option<usize> { // = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉ // QED // Proof of no overflow: - // usize::MAX >= u16::MAX - // usize::MAX = 2^i - 1 where i is any integer >= 16 (due to above) - // Suppose n < 3 * 2^(i-2), then: - // ⌈4n/3⌉ < ⌈4*(3*2^(i-2))/3⌉ = ⌈2^i⌉ - // = 2^i - // = usize::MAX + 1 - // thus ignoring intermediate calcuations, the maximum possible value is usize::MAX thus overflow is not - // possible. + // `MAX_ENCODE_INPUT_LEN` = decode_len(usize::MAX).unwrap(); thus all values less than or equal to + // `MAX_ENCODE_INPUT_LEN` won't overflow ignoring intermediate calcuations since ⌈4n/3⌉ is a + // monotonically increasing function. // QED // Naively implementing ⌈4n/3⌉ as (4 * n).div_ceil(3) can cause overflow due to `4 * n`; thus // we implement the equivalent equation 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉ instead: // `(4 * (n / 3)) + (4 * (n % 3)).div_ceil(3)` since none of the intermediate calculations suffer // from overflow. - // `MAX_ENCODE_INPUT_LEN` = 3 * 2^(i-2) - 1. if input_length <= MAX_ENCODE_INPUT_LEN { // (n / 3) << 2u8 <= m <= usize::MAX; thus the left operand of + is fine. // n % 3 <= 2 @@ -1967,20 +1961,24 @@ mod test { use super::MAX_ENCODE_INPUT_LEN; #[cfg(feature = "alloc")] use alloc::string::String; - #[cfg(feature = "alloc")] - use core::fmt; use rand::{Rng as _, SeedableRng as _, rngs::SmallRng}; + #[expect( + clippy::as_conversions, + clippy::cast_possible_truncation, + reason = "comment justifies correctness" + )] #[cfg(any( target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64", ))] - #[ignore] + #[ignore = "slow"] #[test] fn encode_decode_len() { assert_eq!(MAX_ENCODE_INPUT_LEN, 3 * (usize::MAX.div_ceil(4)) - 1); let mut rng = SmallRng::from_os_rng(); - for _ in 0..10_000_000 { + for _ in 0u32..10_000_000 { + // `uN as usize` is fine since we `cfg` by pointer width. #[cfg(target_pointer_width = "16")] let len = rng.random::<u16>() as usize; #[cfg(target_pointer_width = "32")] @@ -2012,14 +2010,14 @@ mod test { } assert!(super::encode_len_checked(usize::MAX).is_none()); assert!(super::encode_len_checked(MAX_ENCODE_INPUT_LEN).is_some_and(|l| l == usize::MAX)); - for _ in 0..10_000_000 { + for _ in 0u32..10_000_000 { #[cfg(target_pointer_width = "16")] let len = rng.random::<u16>() as usize; #[cfg(target_pointer_width = "32")] let len = rng.random::<u32>() as usize; #[cfg(target_pointer_width = "64")] let len = rng.random::<u64>() as usize; - if len % 4 == 1 { + if len & 3 == 1 { assert!(super::decode_len(len).is_none()); } else { assert!( @@ -2030,7 +2028,7 @@ mod test { } } for i in 0..1025 { - if i % 4 == 1 { + if i & 3 == 1 { assert!(super::decode_len(i).is_none()); } else { assert!( @@ -2042,7 +2040,7 @@ mod test { } #[cfg(target_pointer_width = "16")] for i in 0..=usize::MAX { - if i % 4 == 1 { + if i & 3 == 1 { assert!(super::decode_len(i).is_none()); } else { assert!(super::decode_len(i).is_some_and(|l| super::encode_len_checked(l).is_some_and(|orig| orig == i))); @@ -2050,7 +2048,7 @@ mod test { } #[cfg(not(target_pointer_width = "16"))] for i in usize::MAX - 1_000_000..=usize::MAX { - if i % 4 == 1 { + if i & 3 == 1 { assert!(super::decode_len(i).is_none()); } else { assert!(super::decode_len(i).is_some_and(|l| super::encode_len_checked(l).is_some_and(|orig| orig == i))); @@ -2058,29 +2056,33 @@ mod test { } assert!(super::decode_len(usize::MAX).is_some_and(|l| l == MAX_ENCODE_INPUT_LEN)); } + #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] #[cfg(feature = "alloc")] #[test] - fn encode_write() -> fmt::Result { + fn encode_write() { let input = [9; 8192]; let mut buffer = String::with_capacity(super::encode_len(input.len())); - let cap = buffer.capacity() as isize; + let cap = buffer.capacity(); let mut write_len; for len in 0..input.len() { - write_len = super::encode_len(len) as isize; - match write_len.checked_add(buffer.len() as isize) { + write_len = super::encode_len(len); + match write_len.checked_add(buffer.len()) { None => { buffer.clear(); - super::encode_write(&input[..len], &mut buffer)?; - assert_eq!(buffer.len() as isize, write_len); + // Indexing is fine since `len <= input.len()`. + assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(())); + assert_eq!(buffer.len(), write_len); } Some(l) => { if l > cap { buffer.clear(); - super::encode_write(&input[..len], &mut buffer)?; - assert_eq!(buffer.len() as isize, write_len); + // Indexing is fine since `len <= input.len()`. + assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(())); + assert_eq!(buffer.len(), write_len); } else { - super::encode_write(&input[..len], &mut buffer)?; - assert_eq!(buffer.len() as isize, l); + // Indexing is fine since `len <= input.len()`. + assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(())); + assert_eq!(buffer.len(), l); } } } @@ -2091,6 +2093,5 @@ mod test { .all(|b| { matches!(*b, b'C' | b'J' | b'Q' | b'k') }) ); } - Ok(()) } }