commit d4d6190c1cf37ec44e0cb488326425ca1b669227
parent 64c4859714b249442affa4104c67a0da57a08300
Author: Zack Newman <zack@philomathiclife.com>
Date: Sat, 6 Jun 2026 15:38:48 -0600
update deps, move unit tests, simplify encode_write
Diffstat:
| M | Cargo.toml | | | 8 | +++++--- |
| M | README.md | | | 3 | ++- |
| M | src/lib.rs | | | 318 | ++++++++++++++++--------------------------------------------------------------- |
| A | src/test.rs | | | 153 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
4 files changed, 225 insertions(+), 257 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -9,8 +9,8 @@ license = "MIT OR Apache-2.0"
name = "base64url_nopad"
readme = "README.md"
repository = "https://git.philomathiclife.com/repos/base64url_nopad/"
-rust-version = "1.95.0"
-version = "0.1.5"
+rust-version = "1.96.0"
+version = "0.2.0"
[lints.rust]
deprecated-safe = { level = "deny", priority = -1 }
@@ -33,6 +33,7 @@ deref-into-dyn-supertrait = { level = "deny", priority = -1 }
ffi-unwind-calls = { level = "deny", priority = -1 }
#fuzzy-provenance-casts = { level = "deny", priority = -1 }
impl-trait-redundant-captures = { level = "deny", priority = -1 }
+linker-info = { 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 }
@@ -84,6 +85,7 @@ missing_trait_methods = "allow"
question_mark_used = "allow"
ref_patterns = "allow"
return_and_then = "allow"
+single_call_fn = "allow"
single_char_lifetime_names = "allow"
unseparated_literal_suffix = "allow"
@@ -107,7 +109,7 @@ targets = [
]
[dev-dependencies]
-rand = { version = "0.10.0", default-features = false, features = ["sys_rng"] }
+rand = { version = "0.10.1", default-features = false, features = ["sys_rng"] }
### FEATURES #################################################################
diff --git a/README.md b/README.md
@@ -15,7 +15,7 @@ _including_ `panic`s related to memory allocations.
use base64url_nopad::DecodeErr;
/// Length of our input to encode.
const INPUT_LEN: usize = 259;
-/// The base64url encoded value without padding of our input.
+/// The base64url-encoded value without padding of our input.
const ENCODED_VAL: &str = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_1MH0Q";
fn main() -> Result<(), DecodeErr> {
let mut input = [0; INPUT_LEN];
@@ -30,6 +30,7 @@ fn main() -> Result<(), DecodeErr> {
assert!(base64url_nopad::decode_len(output.len()).is_some_and(|len| len == INPUT_LEN));
base64url_nopad::decode_buffer(ENCODED_VAL.as_bytes(), &mut output[..INPUT_LEN])?;
assert_eq!(input, output[..INPUT_LEN]);
+ Ok(())
}
```
diff --git a/src/lib.rs b/src/lib.rs
@@ -68,6 +68,9 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(any(doc, feature = "alloc"))]
extern crate alloc;
+/// Unit tests.
+#[cfg(test)]
+mod test;
#[cfg(any(doc, feature = "alloc"))]
use alloc::{collections::TryReserveError, string::String, vec::Vec};
use core::{
@@ -783,6 +786,17 @@ pub const fn encode_len(input_length: usize) -> usize {
// A precondition for calling this function is to ensure `encode_len_checked` can't return `None`.
encode_len_checked(input_length).unwrap()
}
+/// `const`-version of `unreachable`.
+macro_rules! impossible {
+ ( $( $x:literal)? ) => {
+ {
+ cold_path();
+ $(
+ panic!($x);
+ )?
+ }
+ };
+}
/// Encodes `input` into `output` re-interpreting the encoded subset of `output` as a `str` before returning it.
///
/// `Some` is returned iff `output.len()` is large enough to write the encoded data into.
@@ -815,11 +829,6 @@ pub const fn encode_len(input_length: usize) -> usize {
/// ```
#[expect(unsafe_code, reason = "comments justify correctness")]
#[expect(
- clippy::missing_panics_doc,
- clippy::panic,
- reason = "false positive in that the panic is impossible modulo bugs"
-)]
-#[expect(
clippy::missing_asserts_for_indexing,
reason = "trust the compiler to already optimize since we match on the length"
)]
@@ -889,10 +898,7 @@ pub const fn encode_buffer_checked<'a>(input: &[u8], output: &'a mut [u8]) -> Op
// `ASCII.len() == 64 > (snd & 15) << 2`, so indexing won't `panic`.
output[output_idx + 2] = ASCII[((snd & 15) << 2) as usize];
}
- _ => {
- cold_path();
- panic!("there is a bug in core::slice::as_chunks");
- }
+ _ => impossible!("there is a bug in core::slice::as_chunks"),
}
// SAFETY:
// We verified `output.len() >= final_len`.
@@ -1030,8 +1036,7 @@ pub fn encode(input: &[u8]) -> String {
}
/// Writes the base64url encoding of `input` using `writer`.
///
-/// Internally a buffer of at most 1024 bytes is used to write the encoded data. Smaller buffers may be used
-/// for small inputs.
+/// Internally a buffer of at most 1024 bytes is used to write the encoded data.
///
/// # Errors
///
@@ -1069,17 +1074,11 @@ pub fn encode(input: &[u8]) -> String {
reason = "comments justify correctness"
)]
#[inline]
-pub fn encode_write<W: Write>(mut input: &[u8], writer: &mut W) -> fmt::Result {
- /// The minimum buffer size we use.
- const MIN_BUFFER_LEN: usize = 256;
- /// The medium buffer size we use.
- const MID_BUFFER_LEN: usize = MIN_BUFFER_LEN << 1;
+pub fn encode_write<W: Write>(mut input: &[u8], mut writer: W) -> fmt::Result {
/// The max buffer size.
///
/// This must be at least 4, no more than `i16::MAX`, and must be a power of 2.
- const MAX_BUFFER_LEN: usize = MID_BUFFER_LEN << 1;
- /// The minimum length of the input until we must chunk encode the data.
- const LOOP_LEN: usize = MAX_BUFFER_LEN + 1;
+ const MAX_BUFFER_LEN: usize = 1024;
/// Want to ensure at compilation time that `MAX_BUFFER_LEN` upholds its invariants. Namely
/// that it's at least as large as 4, doesn't exceed [`i16::MAX`], and is always a power of 2.
const _: () = {
@@ -1095,55 +1094,36 @@ pub fn encode_write<W: Write>(mut input: &[u8], writer: &mut W) -> fmt::Result {
// This will never `panic` since `MAX_BUFFER_LEN` is a power of two at least as large as 4
// (i.e., `MAX_BUFFER_LEN` ≢ 1 (mod 4)).
const INPUT_LEN: usize = decode_len(MAX_BUFFER_LEN).unwrap();
+ let mut buffer = [0; MAX_BUFFER_LEN];
// This won't `panic` since `input.len()` is guaranteed to be no more than `isize::MAX`.
let len = encode_len(input.len());
- match len {
- 0 => Ok(()),
- 1..=MIN_BUFFER_LEN => {
- let mut buffer = [0; MIN_BUFFER_LEN];
- // `buffer.len() == MIN_BUFFER_LEN >= len`, so indexing is fine.
- // `encode_buffer` won't `panic` since `len` is the exact number of bytes needed to encode
- // the data.
- writer.write_str(encode_buffer(input, &mut buffer[..len]))
- }
- 257..=MID_BUFFER_LEN => {
- let mut buffer = [0; MID_BUFFER_LEN];
- // `buffer.len() == MID_BUFFER_LEN >= len`, so indexing is fine.
- // `encode_buffer` won't `panic` since `len` is the exact number of bytes needed to encode
- // the data.
- writer.write_str(encode_buffer(input, &mut buffer[..len]))
- }
- 513..=MAX_BUFFER_LEN => {
- let mut buffer = [0; MAX_BUFFER_LEN];
- // `buffer.len() == MAX_BUFFER_LEN >= len`, so indexing is fine.
- // `encode_buffer` won't `panic` since `len` is the exact number of bytes needed to encode
- // the data.
- writer.write_str(encode_buffer(input, &mut buffer[..len]))
- }
- LOOP_LEN.. => {
- let mut buffer = [0; MAX_BUFFER_LEN];
- let mut counter = 0;
- // `len / MAX_BUFFER_LEN` is equal to ⌊len / MAX_BUFFER_LEN⌋ since `MAX_BUFFER_LEN` is a power of two.
- // We can safely encode `term` chunks of `INPUT_LEN` length into `buffer`.
- let term = len >> MAX_BUFFER_LEN.trailing_zeros();
- let mut input_buffer;
- while counter < term {
- // SAFETY:
- // `input.len() >= INPUT_LEN`.
- input_buffer = unsafe { input.split_at_unchecked(INPUT_LEN) };
- // `encode_buffer` won't `panic` since `buffer` has length `MAX_BUFFER_LEN` which
- // is the exact length needed for `INPUT_LEN` length inputs which `input_buffer.0` is.
- writer.write_str(encode_buffer(input_buffer.0, buffer.as_mut_slice()))?;
- input = input_buffer.1;
- // `counter < term`, so overflow cannot happen.
- counter += 1;
- }
- // `encode_len` won't `panic` since `input.len() < MAX_ENCODE_INPUT_LEN`.
- // `input.len() < INPUT_LEN`; thus `encode_len(input.len()) < MAX_BUFFER_LEN = buffer.len()` so
- // indexing is fine.
- // `encode_buffer` won't `panic` since the buffer is the exact length needed to encode `input`.
- writer.write_str(encode_buffer(input, &mut buffer[..encode_len(input.len())]))
+ if len <= MAX_BUFFER_LEN {
+ // `buffer.len() == MAX_BUFFER_LEN >= len`, so indexing is fine.
+ // `encode_buffer` won't `panic` since `len` is the exact number of bytes needed to encode
+ // the data.
+ writer.write_str(encode_buffer(input, &mut buffer[..len]))
+ } else {
+ let mut counter = 0;
+ // `len / MAX_BUFFER_LEN` is equal to ⌊len / MAX_BUFFER_LEN⌋ since `MAX_BUFFER_LEN` is a power of two.
+ // We can safely encode `term` chunks of `INPUT_LEN` length into `buffer`.
+ let term = len >> MAX_BUFFER_LEN.trailing_zeros();
+ let mut input_buffer;
+ while counter < term {
+ // SAFETY:
+ // `input.len() >= INPUT_LEN`.
+ input_buffer = unsafe { input.split_at_unchecked(INPUT_LEN) };
+ // `encode_buffer` won't `panic` since `buffer` has length `MAX_BUFFER_LEN` which
+ // is the exact length needed for `INPUT_LEN` length inputs which `input_buffer.0` is.
+ writer.write_str(encode_buffer(input_buffer.0, buffer.as_mut_slice()))?;
+ input = input_buffer.1;
+ // `counter < term`, so overflow cannot happen.
+ counter += 1;
}
+ // `encode_len` won't `panic` since `input.len() < MAX_ENCODE_INPUT_LEN`.
+ // `input.len() < INPUT_LEN`; thus `encode_len(input.len()) < MAX_BUFFER_LEN = buffer.len()` so
+ // indexing is fine.
+ // `encode_buffer` won't `panic` since the buffer is the exact length needed to encode `input`.
+ writer.write_str(encode_buffer(input, &mut buffer[..encode_len(input.len())]))
}
}
/// Appends the base64url encoding of `input` to `s` returning the `str` that was appended.
@@ -1442,12 +1422,6 @@ impl Error for DecodeErr {}
/// ```
#[expect(unsafe_code, reason = "comment justifies correctness")]
#[expect(
- clippy::missing_panics_doc,
- clippy::panic,
- clippy::panic_in_result_fn,
- reason = "want to crash when there is a bug"
-)]
-#[expect(
clippy::missing_asserts_for_indexing,
reason = "trust the compiler to already optimize since we match on the length"
)]
@@ -1489,10 +1463,7 @@ pub const fn decode_buffer<'a>(
}
match rem.len() {
0 => {}
- 1 => {
- cold_path();
- panic!("there is a bug in base64url_nopad::decode_len");
- }
+ 1 => impossible!("there is a bug in base64url_nopad::decode_len"),
2 => {
if let Some(base64_fst) = Alphabet::from_ascii(rem[0])
&& let Some(base64_snd) = Alphabet::from_ascii(rem[1])
@@ -1527,10 +1498,7 @@ pub const fn decode_buffer<'a>(
return Err(DecodeErr::InvalidByte);
}
}
- _ => {
- cold_path();
- panic!("there is a bug in core::slice::as_chunks");
- }
+ _ => impossible!("there is a bug in core::slice::as_chunks"),
}
// SAFETY:
// `output.len() >= output_len`.
@@ -1624,12 +1592,6 @@ pub fn decode(input: &[u8]) -> Result<Vec<u8>, DecodeErr> {
/// # Ok::<_, DecodeErr>(())
/// ```
#[expect(
- clippy::missing_panics_doc,
- clippy::panic,
- clippy::panic_in_result_fn,
- reason = "want to crash when there is a bug"
-)]
-#[expect(
clippy::missing_asserts_for_indexing,
reason = "trust the compiler to already optimize since we match on the length"
)]
@@ -1686,10 +1648,7 @@ pub const fn validate_encoded_data(input: &[u8]) -> Result<(), DecodeErr> {
Err(DecodeErr::InvalidByte)
}
}
- _ => {
- cold_path();
- panic!("there is a bug in core::slice::as_chunks");
- }
+ _ => impossible!("there is a bug in core::slice::as_chunks"),
}
}
/// Same as [`encode_buffer`] except `output` must have the _exact_ length needed to encode `input`, and the
@@ -1728,8 +1687,9 @@ pub const fn encode_buffer_exact(input: &[u8], output: &mut [u8]) {
///
/// # Panics
///
-/// `panic`s iff `output` does not have the _exact_ length needed to contain the decoded data. Note when `input`
-/// contains an invalid length, [`DecodeErr::EncodedLen`] is returned _not_ a `panic`.
+/// `panic`s iff `output` does not have the _exact_ length needed to contain the decoded data and `input`
+/// has a valid length (i.e., [`DecodeErr::EncodedLen`] is returned _not_ a `panic` when `input` has invalid
+/// length).
///
/// # Examples
///
@@ -1755,178 +1715,30 @@ pub const fn encode_buffer_exact(input: &[u8], output: &mut [u8]) {
/// # Ok::<_, DecodeErr>(())
/// ```
#[expect(
+ clippy::panic,
clippy::panic_in_result_fn,
reason = "purpose of this function is to panic when output does not have the exact length needed"
)]
#[inline]
pub const fn decode_buffer_exact(input: &[u8], output: &mut [u8]) -> Result<(), DecodeErr> {
- if let Some(output_len) = decode_len(input.len()) {
- assert!(
- output.len() == output_len,
- "decode_buffer_exact must be passed an output buffer whose length is exactly the length needed to decode the data"
- );
- if let Err(e) = decode_buffer(input, output) {
- Err(e)
- } else {
- Ok(())
- }
- } else {
- Err(DecodeErr::EncodedLen)
- }
-}
-#[cfg(test)]
-mod test {
- #[cfg(any(
- target_pointer_width = "16",
- target_pointer_width = "32",
- target_pointer_width = "64",
- ))]
- use super::MAX_ENCODE_INPUT_LEN;
- #[cfg(feature = "alloc")]
- use alloc::string::String;
- #[cfg(any(
- target_pointer_width = "16",
- target_pointer_width = "32",
- target_pointer_width = "64",
- ))]
- use rand::{RngExt 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 = "slow"]
- #[test]
- fn encode_decode_len() {
- assert_eq!(MAX_ENCODE_INPUT_LEN, 3 * (usize::MAX.div_ceil(4)) - 1);
- let mut rng = rand::make_rng::<SmallRng>();
- 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")]
- let len = rng.random::<u32>() as usize;
- #[cfg(target_pointer_width = "64")]
- let len = rng.random::<u64>() as usize;
- if len <= MAX_ENCODE_INPUT_LEN {
- assert_eq!(
- super::encode_len_checked(len).map(super::decode_len),
- Some(Some(len))
- );
- } else {
- assert!(super::encode_len_checked(len).is_none());
- }
- }
- for i in 0..1025 {
- assert_eq!(
- super::encode_len_checked(i).map(super::decode_len),
- Some(Some(i))
+ let output_len = output.len();
+ match decode_buffer(input, output) {
+ Ok(v) => {
+ assert!(
+ v.len() == output_len,
+ "decode_buffer_exact must be passed an output buffer whose length is exactly the length needed to decode the data"
);
+ Ok(())
}
- #[cfg(target_pointer_width = "16")]
- for i in MAX_ENCODE_INPUT_LEN + 1.. {
- assert!(super::encode_len_checked(i).is_none());
- }
- #[cfg(not(target_pointer_width = "16"))]
- for i in MAX_ENCODE_INPUT_LEN + 1..MAX_ENCODE_INPUT_LEN + 1_000_000 {
- assert!(super::encode_len_checked(i).is_none());
- }
- assert!(super::encode_len_checked(usize::MAX).is_none());
- assert_eq!(
- super::encode_len_checked(MAX_ENCODE_INPUT_LEN),
- Some(usize::MAX)
- );
- 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 & 3 == 1 {
- assert!(super::decode_len(len).is_none());
- } else {
- assert_eq!(
- super::decode_len(len).map(super::encode_len_checked),
- Some(Some(len))
- );
- }
- }
- for i in 0..1025 {
- if i & 3 == 1 {
- assert!(super::decode_len(i).is_none());
- } else {
- assert_eq!(
- super::decode_len(i).map(super::encode_len_checked),
- Some(Some(i))
- );
- }
- }
- #[cfg(target_pointer_width = "16")]
- for i in 0..=usize::MAX {
- if i & 3 == 1 {
- assert!(super::decode_len(i).is_none());
- } else {
- assert_eq!(
- super::decode_len(i).map(super::encode_len_checked),
- Some(Some(i))
+ Err(e) => {
+ if matches!(e, DecodeErr::BufferLen) {
+ cold_path();
+ panic!(
+ "decode_buffer_exact must be passed an output buffer whose length is exactly the length needed to decode the data"
);
- }
- }
- #[cfg(not(target_pointer_width = "16"))]
- for i in usize::MAX - 1_000_000..=usize::MAX {
- if i & 3 == 1 {
- assert!(super::decode_len(i).is_none());
} else {
- assert_eq!(
- super::decode_len(i).map(super::encode_len_checked),
- Some(Some(i))
- );
+ Err(e)
}
}
- assert_eq!(super::decode_len(usize::MAX), Some(MAX_ENCODE_INPUT_LEN));
- }
- #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
- #[cfg(feature = "alloc")]
- #[test]
- fn encode_write() {
- let input = [9; 8192];
- let mut buffer = String::with_capacity(super::encode_len(input.len()));
- let cap = buffer.capacity();
- let mut write_len;
- for len in 0..input.len() {
- write_len = super::encode_len(len);
- match write_len.checked_add(buffer.len()) {
- None => {
- buffer.clear();
- // 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();
- // Indexing is fine since `len <= input.len()`.
- assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(()));
- assert_eq!(buffer.len(), write_len);
- } else {
- // Indexing is fine since `len <= input.len()`.
- assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(()));
- assert_eq!(buffer.len(), l);
- }
- }
- }
- assert!(
- buffer
- .as_bytes()
- .iter()
- .all(|b| { matches!(*b, b'C' | b'J' | b'Q' | b'k') })
- );
- }
}
}
diff --git a/src/test.rs b/src/test.rs
@@ -0,0 +1,153 @@
+#[cfg(any(
+ target_pointer_width = "16",
+ target_pointer_width = "32",
+ target_pointer_width = "64",
+))]
+use super::MAX_ENCODE_INPUT_LEN;
+#[cfg(feature = "alloc")]
+use alloc::string::String;
+#[cfg(any(
+ target_pointer_width = "16",
+ target_pointer_width = "32",
+ target_pointer_width = "64",
+))]
+use rand::{RngExt 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 = "slow"]
+#[test]
+fn encode_decode_len() {
+ assert_eq!(MAX_ENCODE_INPUT_LEN, 3 * (usize::MAX.div_ceil(4)) - 1);
+ let mut rng = rand::make_rng::<SmallRng>();
+ 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")]
+ let len = rng.random::<u32>() as usize;
+ #[cfg(target_pointer_width = "64")]
+ let len = rng.random::<u64>() as usize;
+ if len <= MAX_ENCODE_INPUT_LEN {
+ assert_eq!(
+ super::encode_len_checked(len).map(super::decode_len),
+ Some(Some(len))
+ );
+ } else {
+ assert!(super::encode_len_checked(len).is_none());
+ }
+ }
+ for i in 0..1025 {
+ assert_eq!(
+ super::encode_len_checked(i).map(super::decode_len),
+ Some(Some(i))
+ );
+ }
+ #[cfg(target_pointer_width = "16")]
+ for i in MAX_ENCODE_INPUT_LEN + 1.. {
+ assert!(super::encode_len_checked(i).is_none());
+ }
+ #[cfg(not(target_pointer_width = "16"))]
+ for i in MAX_ENCODE_INPUT_LEN + 1..MAX_ENCODE_INPUT_LEN + 1_000_000 {
+ assert!(super::encode_len_checked(i).is_none());
+ }
+ assert!(super::encode_len_checked(usize::MAX).is_none());
+ assert_eq!(
+ super::encode_len_checked(MAX_ENCODE_INPUT_LEN),
+ Some(usize::MAX)
+ );
+ 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 & 3 == 1 {
+ assert!(super::decode_len(len).is_none());
+ } else {
+ assert_eq!(
+ super::decode_len(len).map(super::encode_len_checked),
+ Some(Some(len))
+ );
+ }
+ }
+ for i in 0..1025 {
+ if i & 3 == 1 {
+ assert!(super::decode_len(i).is_none());
+ } else {
+ assert_eq!(
+ super::decode_len(i).map(super::encode_len_checked),
+ Some(Some(i))
+ );
+ }
+ }
+ #[cfg(target_pointer_width = "16")]
+ for i in 0..=usize::MAX {
+ if i & 3 == 1 {
+ assert!(super::decode_len(i).is_none());
+ } else {
+ assert_eq!(
+ super::decode_len(i).map(super::encode_len_checked),
+ Some(Some(i))
+ );
+ }
+ }
+ #[cfg(not(target_pointer_width = "16"))]
+ for i in usize::MAX - 1_000_000..=usize::MAX {
+ if i & 3 == 1 {
+ assert!(super::decode_len(i).is_none());
+ } else {
+ assert_eq!(
+ super::decode_len(i).map(super::encode_len_checked),
+ Some(Some(i))
+ );
+ }
+ }
+ assert_eq!(super::decode_len(usize::MAX), Some(MAX_ENCODE_INPUT_LEN));
+}
+#[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
+#[cfg(feature = "alloc")]
+#[test]
+fn encode_write() {
+ let input = [9; 8192];
+ let mut buffer = String::with_capacity(super::encode_len(input.len()));
+ let cap = buffer.capacity();
+ let mut write_len;
+ for len in 0..input.len() {
+ write_len = super::encode_len(len);
+ match write_len.checked_add(buffer.len()) {
+ None => {
+ buffer.clear();
+ // 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();
+ // Indexing is fine since `len <= input.len()`.
+ assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(()));
+ assert_eq!(buffer.len(), write_len);
+ } else {
+ // Indexing is fine since `len <= input.len()`.
+ assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(()));
+ assert_eq!(buffer.len(), l);
+ }
+ }
+ }
+ assert!(
+ buffer
+ .as_bytes()
+ .iter()
+ .all(|b| { matches!(*b, b'C' | b'J' | b'Q' | b'k') })
+ );
+ }
+}