Sign In

base64

Package Overview
Dependencies
Maintainers
1
Versions
45
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

base64 - cargo Package Compare versions

Comparing version
0.22.1
to
0.23.0
+807
src/engine/simd.rs
//! SIMD-accelerated engines for the standard and URL-safe alphabets.
//!
//! These are gated behind the `simd-unsafe` feature because they use `unsafe`. Three engines are
//! provided:
//!
//! - `Simd` detects the best available instruction set at runtime and falls back to the scalar
//! [`GeneralPurpose`] engine when none is available. It requires `std` for the detection.
//! - `Avx2` and `Neon` target a specific instruction set with no runtime detection, so they can
//! be used in `no_std` builds when the target is known to support the instructions.
//!
//! Only the STANDARD and URL_SAFE alphabets are accelerated (they share indices `0..=61` and differ
//! only at `62`/`63`). Each engine therefore has dedicated `standard` / `url_safe` constructors
//! rather than taking an arbitrary [`Alphabet`](crate::alphabet::Alphabet); use [`GeneralPurpose`]
//! for any other alphabet.
//!
//! The kernels follow Wojciech Mula's vectorized base64 algorithms
//! (<http://0x80.pl/notesen/2016-01-17-sse-base64-decoding.html> and the companion encoding note).
//! AVX2 uses the multiply-based bit (de)interleave; NEON, which lacks the relevant multiplies, uses
//! the shift/mask variant. Both share the same per-alphabet lookup tables, expressed as the
//! associated constants of the `SimdAlphabet` trait so the kernels can inline them.
#![allow(unsafe_code)]
use crate::alphabet::Symbol;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
use crate::{
engine::{
general_purpose::{
decode::decode_helper, encode_helper, GeneralPurpose, GeneralPurposeConfig,
GeneralPurposeEstimate,
},
DecodeMetadata, Engine,
},
DecodeSliceError,
};
/// A base64 alphabet family the SIMD kernels can accelerate.
///
/// Carries the per-alphabet lookup tables as associated constants. Private (hence sealed);
/// implemented only by [`Standard`] and [`UrlSafe`], selected at runtime by [`SimdKind`].
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
trait SimdAlphabet {
/// pshufb encode table: maps a reduced 6-bit index to `ascii - index`.
const ENCODE_LUT: [i8; 16];
/// pshufb decode table indexed by the high nibble; added to the byte to produce its 6-bit value.
const DECODE_SHIFT_LUT: [i8; 16];
/// pshufb decode table indexed by the low nibble; bit `hi` marks `(hi, lo)` as a valid symbol.
const DECODE_MASK_LUT: [u8; 16];
/// The high-`62`/`63` symbol whose shift needs the fixup below (`+`/`-`).
const DECODE_FIXUP_CHAR: i8;
/// The shift applied to [`DECODE_FIXUP_CHAR`](Self::DECODE_FIXUP_CHAR).
const DECODE_FIXUP_SHIFT: i8;
}
/// The STANDARD alphabet family (`+`/`/` at 62/63).
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
enum Standard {}
/// The URL_SAFE alphabet family (`-`/`_` at 62/63).
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
enum UrlSafe {}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
impl SimdAlphabet for Standard {
const ENCODE_LUT: [i8; 16] = [
65, 71, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -19, -16, 0, 0,
];
const DECODE_SHIFT_LUT: [i8; 16] = [0, 0, 19, 4, -65, -65, -71, -71, 0, 0, 0, 0, 0, 0, 0, 0];
const DECODE_MASK_LUT: [u8; 16] = [
0xA8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF0, 0x54, 0x50, 0x50, 0x50,
0x54,
];
const DECODE_FIXUP_CHAR: i8 = 0x2F;
const DECODE_FIXUP_SHIFT: i8 = 16;
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
impl SimdAlphabet for UrlSafe {
const ENCODE_LUT: [i8; 16] = [
65, 71, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -17, 32, 0, 0,
];
const DECODE_SHIFT_LUT: [i8; 16] = [0, 0, 17, 4, -65, -65, -71, -71, 0, 0, 0, 0, 0, 0, 0, 0];
const DECODE_MASK_LUT: [u8; 16] = [
0xA8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF0, 0x50, 0x50, 0x54, 0x50,
0x70,
];
const DECODE_FIXUP_CHAR: i8 = 0x5F;
const DECODE_FIXUP_SHIFT: i8 = -32;
}
/// Which accelerated alphabet family an engine uses. Selects the kernel monomorphization at runtime.
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum SimdKind {
Standard,
UrlSafe,
}
/// Minimum input length before a SIMD path is used. Below these the setup cost outweighs the gain;
/// encode needs more data than decode to break even.
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
const SIMD_MIN_INPUT_ENCODE: usize = 128;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
const SIMD_MIN_INPUT_DECODE: usize = 64;
/// pshufb decode validity table: maps a high nibble to a single set bit. Shared by both alphabets.
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
const BITPOS_LUT: [u8; 16] = [1, 2, 4, 8, 16, 32, 64, 128, 0, 0, 0, 0, 0, 0, 0, 0];
#[cfg(target_arch = "x86_64")]
mod avx2 {
use super::{SimdAlphabet, BITPOS_LUT, SIMD_MIN_INPUT_DECODE, SIMD_MIN_INPUT_ENCODE};
use core::arch::x86_64::*;
/// Encode leading whole 24-byte input groups (24 in -> 32 out per iteration).
///
/// # Safety
///
/// The running CPU must support AVX2.
#[target_feature(enable = "avx2")]
pub(super) unsafe fn encode_bulk<A: SimdAlphabet>(
input: &[u8],
output: &mut [u8],
) -> (usize, usize) {
if input.len() < SIMD_MIN_INPUT_ENCODE {
return (0, 0);
}
let lut_data = A::ENCODE_LUT;
// SAFETY: `lut_data` is a 16-byte array; `_mm_loadu_si128` reads exactly 16 bytes from it.
let lut = _mm256_broadcastsi128_si256(_mm_loadu_si128(lut_data.as_ptr().cast()));
#[rustfmt::skip]
let shuf = _mm256_setr_epi8(
1, 0, 2, 1, 4, 3, 5, 4, 7, 6, 8, 7, 10, 9, 11, 10,
1, 0, 2, 1, 4, 3, 5, 4, 7, 6, 8, 7, 10, 9, 11, 10,
);
let mask_hi = _mm256_set1_epi32(0x0fc0_fc00_u32 as i32);
let mul_hi = _mm256_set1_epi32(0x0400_0040);
let mask_lo = _mm256_set1_epi32(0x003f_03f0);
let mul_lo = _mm256_set1_epi32(0x0100_0010);
let const51 = _mm256_set1_epi8(51);
let const25 = _mm256_set1_epi8(25);
let mut i = 0usize;
let mut o = 0usize;
// A whole 32-byte vector is read but only 24 bytes are consumed, so 32 input bytes must be
// available; the store writes 32 output bytes.
while i + 32 <= input.len() && o + 32 <= output.len() {
// SAFETY: the loop guard ensures `input[i..i+32]` is in bounds, so this reads 32 valid
// bytes; `loadu` has no alignment requirement.
let data = _mm256_loadu_si256(input.as_ptr().add(i).cast());
// TODO https://arxiv.org/abs/1704.00605 doesn't seem to require this perm step, which
// costs 3 cycles, and is slightly different in the reduce phase as well.
// Rearrange dwords so low lane = bytes[0..16], high lane = bytes[12..28].
let perm = _mm256_permutevar8x32_epi32(data, _mm256_setr_epi32(0, 1, 2, 3, 3, 4, 5, 6));
let inb = _mm256_shuffle_epi8(perm, shuf);
let t0 = _mm256_and_si256(inb, mask_hi);
let t1 = _mm256_mulhi_epu16(t0, mul_hi);
let t2 = _mm256_and_si256(inb, mask_lo);
let t3 = _mm256_mullo_epi16(t2, mul_lo);
let indices = _mm256_or_si256(t1, t3); // one 6-bit value (0..=63) per byte
let reduced = _mm256_subs_epu8(indices, const51);
let gt25 = _mm256_cmpgt_epi8(indices, const25);
let reduced = _mm256_sub_epi8(reduced, gt25);
let ascii = _mm256_add_epi8(indices, _mm256_shuffle_epi8(lut, reduced));
// SAFETY: the loop guard ensures `output[o..o+32]` is in bounds; `storeu` writes 32
// bytes with no alignment requirement.
_mm256_storeu_si256(output.as_mut_ptr().add(o).cast(), ascii);
i += 24;
o += 32;
}
(i, o)
}
/// Decode leading whole 32-byte input blocks (32 in -> 24 out per iteration).
///
/// # Safety
///
/// The running CPU must support AVX2.
#[target_feature(enable = "avx2")]
pub(super) unsafe fn decode_bulk<A: SimdAlphabet>(
input: &[u8],
quads_end: usize,
output: &mut [u8],
) -> (usize, usize) {
if quads_end < SIMD_MIN_INPUT_DECODE {
return (0, 0);
}
let shift_data = A::DECODE_SHIFT_LUT;
let mask_data = A::DECODE_MASK_LUT;
// SAFETY: each LUT is a 16-byte array read in full by `_mm_loadu_si128`.
let shift_lut = _mm256_broadcastsi128_si256(_mm_loadu_si128(shift_data.as_ptr().cast()));
let mask_lut = _mm256_broadcastsi128_si256(_mm_loadu_si128(mask_data.as_ptr().cast()));
let bitpos_lut = _mm256_broadcastsi128_si256(_mm_loadu_si128(BITPOS_LUT.as_ptr().cast()));
let low_nibble_mask = _mm256_set1_epi8(0x0f);
let fixup_char = _mm256_set1_epi8(A::DECODE_FIXUP_CHAR);
let fixup_shift = _mm256_set1_epi8(A::DECODE_FIXUP_SHIFT);
let zero = _mm256_setzero_si256();
let merge_mul1 = _mm256_set1_epi32(0x0140_0140);
let merge_mul2 = _mm256_set1_epi32(0x0001_1000);
#[rustfmt::skip]
let pack_shuf = _mm256_setr_epi8(
2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12, -1, -1, -1, -1,
2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12, -1, -1, -1, -1,
);
let lane_compact = _mm256_setr_epi32(0, 1, 2, 4, 5, 6, 6, 6);
let mut i = 0usize;
let mut o = 0usize;
// Need 32 input bytes available to load; the store writes exactly 24 output bytes.
while i + 32 <= quads_end && o + 24 <= output.len() {
// SAFETY: `i + 32 <= quads_end <= input.len()`, so this reads 32 in-bounds bytes;
// `loadu` needs no alignment.
let data = _mm256_loadu_si256(input.as_ptr().add(i).cast());
let hi_nibbles = _mm256_and_si256(_mm256_srli_epi32(data, 4), low_nibble_mask);
let lo_nibbles = _mm256_and_si256(data, low_nibble_mask);
let m = _mm256_shuffle_epi8(mask_lut, lo_nibbles);
let bit = _mm256_shuffle_epi8(bitpos_lut, hi_nibbles);
let non_match = _mm256_cmpeq_epi8(_mm256_and_si256(m, bit), zero);
if _mm256_movemask_epi8(non_match) != 0 {
// Invalid byte in this block; let the scalar decoder report the exact offset.
break;
}
let sh = _mm256_shuffle_epi8(shift_lut, hi_nibbles);
let eq_fixup = _mm256_cmpeq_epi8(data, fixup_char);
let shift = _mm256_blendv_epi8(sh, fixup_shift, eq_fixup);
let values = _mm256_add_epi8(data, shift); // 6-bit value per byte
let merged = _mm256_maddubs_epi16(values, merge_mul1);
let packed = _mm256_madd_epi16(merged, merge_mul2);
let shuffled = _mm256_shuffle_epi8(packed, pack_shuf);
let compact = _mm256_permutevar8x32_epi32(shuffled, lane_compact);
// Store exactly 24 bytes (16 + 8); a wider store would clobber an oversized output.
let lo = _mm256_castsi256_si128(compact);
let hi = _mm256_extracti128_si256(compact, 1);
// SAFETY: the loop guard ensures `o + 24 <= output.len()`, so the 16-byte store at `o`
// and the 8-byte store at `o + 16` are both in bounds; neither needs alignment.
_mm_storeu_si128(output.as_mut_ptr().add(o).cast(), lo);
_mm_storel_epi64(output.as_mut_ptr().add(o + 16).cast(), hi);
i += 32;
o += 24;
}
(i, o)
}
}
#[cfg(target_arch = "aarch64")]
mod neon {
use super::{SimdAlphabet, BITPOS_LUT, SIMD_MIN_INPUT_DECODE, SIMD_MIN_INPUT_ENCODE};
use core::arch::aarch64::*;
/// Encode leading whole 12-byte input groups (12 in -> 16 out per iteration).
///
/// # Safety
///
/// The running CPU must support NEON.
#[target_feature(enable = "neon")]
pub(super) unsafe fn encode_bulk<A: SimdAlphabet>(
input: &[u8],
output: &mut [u8],
) -> (usize, usize) {
if input.len() < SIMD_MIN_INPUT_ENCODE {
return (0, 0);
}
let lut_data = A::ENCODE_LUT;
let split_bytes: [u8; 16] = [1, 0, 2, 1, 4, 3, 5, 4, 7, 6, 8, 7, 10, 9, 11, 10];
// SAFETY: `split_bytes` and `lut_data` are 16-byte arrays, each read in full by `vld1q_u8`.
let split_shuf = vld1q_u8(split_bytes.as_ptr());
let translate = vld1q_u8(lut_data.as_ptr().cast());
// note the in-memory LE byte order will affect how these match the shuffled data bytes
let m1 = vdupq_n_u32(0x0000_fc00);
let m2 = vdupq_n_u32(0x0000_03f0);
let m3 = vdupq_n_u32(0x0fc0_0000);
let m4 = vdupq_n_u32(0x003f_0000);
let c51 = vdupq_n_u8(51);
let c25 = vdupq_n_s8(25);
let mut i = 0usize;
let mut o = 0usize;
// Reads a full 16-byte vector but consumes only 12 bytes, so 16 must be readable; writes
// exactly 16 output bytes.
while i + 16 <= input.len() && o + 16 <= output.len() {
// SAFETY: the loop guard ensures `input[i..i+16]` is in bounds, so this reads 16 valid
// bytes.
let data = vld1q_u8(input.as_ptr().add(i));
let x0 = vreinterpretq_u32_u8(vqtbl1q_u8(data, split_shuf));
// data now is 4 32-bit words in x0, each with the 3 bytes to encode in that word:
// 1 0 2 1
// 4 3 5 4
// 7 6 8 7
// a 9 b a
// select bits in chunks of 6 into their own bytes, treating the input as a bit sequence
// select the left 6 bits of [0, 3, 6, 9] (in byte 1 of the words) and shift until the
// 6 bits are the low 6 bits of byte 0, then cast to be in 2 byte words
let x1 = vshrq_n_u16::<10>(vreinterpretq_u16_u32(vandq_u32(x0, m1)));
// right 2 bits of [0 3 6 9], left 4 bits of [1 4 7 a], shifted to low bits
let x2 = vshlq_n_u16::<4>(vreinterpretq_u16_u32(vandq_u32(x0, m2)));
// right 4 bits of [1 4 7 a], left 2 bits of [2 5 8 b]
let x3 = vshrq_n_u16::<6>(vreinterpretq_u16_u32(vandq_u32(x0, m3)));
// right 6 bits of [2 5 8 b]
let x4 = vshlq_n_u16::<8>(vreinterpretq_u16_u32(vandq_u32(x0, m4)));
let indices = vreinterpretq_u8_u16(vorrq_u16(vorrq_u16(x1, x2), vorrq_u16(x3, x4)));
// reduce the 6-bit index to a translate-LUT index, then add the offset to get ascii
let reduced = vqsubq_u8(indices, c51);
let gt25 = vcgtq_s8(vreinterpretq_s8_u8(indices), c25);
let reduced = vsubq_u8(reduced, gt25); // subtracting 0xFF adds 1 where index > 25
let ascii = vaddq_u8(indices, vqtbl1q_u8(translate, reduced));
// SAFETY: the loop guard ensures `output[o..o+16]` is in bounds, so this writes 16 bytes
// in bounds.
vst1q_u8(output.as_mut_ptr().add(o), ascii);
i += 12;
o += 16;
}
(i, o)
}
/// Decode leading whole 16-byte input blocks (16 in -> 12 out per iteration).
///
/// # Safety
///
/// The running CPU must support NEON.
#[target_feature(enable = "neon")]
pub(super) unsafe fn decode_bulk<A: SimdAlphabet>(
input: &[u8],
quads_end: usize,
output: &mut [u8],
) -> (usize, usize) {
if quads_end < SIMD_MIN_INPUT_DECODE {
return (0, 0);
}
// SAFETY: each LUT is a 16-byte array read in full by `vld1q_u8`.
let shift_lut = vld1q_u8(A::DECODE_SHIFT_LUT.as_ptr().cast());
let mask_lut = vld1q_u8(A::DECODE_MASK_LUT.as_ptr());
let bitpos_lut = vld1q_u8(BITPOS_LUT.as_ptr());
let low_nibble_mask = vdupq_n_u8(0x0f);
let fixup_char_v = vdupq_n_u8(A::DECODE_FIXUP_CHAR as u8);
let fixup_shift_v = vdupq_n_u8(A::DECODE_FIXUP_SHIFT as u8);
let zero = vdupq_n_u8(0);
let mm1 = vdupq_n_u32(0x003f_003f);
let mm2 = vdupq_n_u32(0x3f00_3f00);
let out_mask = vdupq_n_u32(0x00ff_ffff);
let pack_bytes: [u8; 16] = [
2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12, 0x80, 0x80, 0x80, 0x80,
];
// SAFETY: `pack_bytes` is a 16-byte array read in full by `vld1q_u8`.
let pack_shuf = vld1q_u8(pack_bytes.as_ptr());
let mut i = 0usize;
let mut o = 0usize;
// Need 16 input bytes to load; writes exactly 12 output bytes.
while i + 16 <= quads_end && o + 12 <= output.len() {
// SAFETY: `i + 16 <= quads_end <= input.len()`, so this reads 16 in-bounds bytes.
let data = vld1q_u8(input.as_ptr().add(i));
let hi = vshrq_n_u8::<4>(data);
let lo = vandq_u8(data, low_nibble_mask);
let m = vqtbl1q_u8(mask_lut, lo);
let bit = vqtbl1q_u8(bitpos_lut, hi);
let non_match = vceqq_u8(vandq_u8(m, bit), zero);
if vmaxvq_u8(non_match) != 0 {
// Invalid byte in this block; let the scalar decoder report the exact offset.
break;
}
let sh = vqtbl1q_u8(shift_lut, hi);
let eq_fixup = vceqq_u8(data, fixup_char_v);
let shift = vbslq_u8(eq_fixup, fixup_shift_v, sh);
let values = vaddq_u8(data, shift); // {00aaaaaa|00bbbbbb|00cccccc|00dddddd} x4
// merge 4x6 bits -> 3 bytes per quad via shift/mask (no multiplies on NEON)
let v = vreinterpretq_u32_u8(values);
let x1 = vandq_u32(v, mm1); // {00aaaaaa|00000000|00cccccc|00000000}
let x2 = vandq_u32(v, mm2); // {00000000|00bbbbbb|00000000|00dddddd}
let x3 = vorrq_u32(vshlq_n_u32::<18>(x1), vshrq_n_u32::<10>(x1));
let x4 = vorrq_u32(vshlq_n_u32::<4>(x2), vshrq_n_u32::<24>(x2));
let merged = vandq_u32(vorrq_u32(x3, x4), out_mask);
let packed = vqtbl1q_u8(vreinterpretq_u8_u32(merged), pack_shuf); // 12 bytes in [0..12)
// Store exactly 12 bytes (8 + 4); a wider store would clobber an oversized output.
// SAFETY: the loop guard ensures `o + 12 <= output.len()`, so the 8-byte store at `o`
// and the 4-byte store at `o + 8` are both in bounds.
vst1_u8(output.as_mut_ptr().add(o), vget_low_u8(packed));
// Can't use vst1q_lane_u32 to directly write the last lane as it requires 4-byte
// alignment, which we don't have.
// Could also shift words and then use vst1_u8 again, but this way is more direct and
// doesn't double-write the middle word.
let tail = vgetq_lane_u32::<2>(vreinterpretq_u32_u8(packed));
core::ptr::write_unaligned(output.as_mut_ptr().add(o + 8).cast::<u32>(), tail);
i += 16;
o += 12;
}
(i, o)
}
}
/// Dispatch the AVX2 encode kernel to the right alphabet monomorphization.
///
/// # Safety
///
/// The running CPU must support AVX2 (the requirement is forwarded to [`avx2::encode_bulk`]).
#[cfg(target_arch = "x86_64")]
#[inline]
unsafe fn avx2_encode(kind: SimdKind, input: &[u8], output: &mut [u8]) -> (usize, usize) {
match kind {
// SAFETY: this function's contract guarantees AVX2, which is all the kernel requires.
SimdKind::Standard => avx2::encode_bulk::<Standard>(input, output),
SimdKind::UrlSafe => avx2::encode_bulk::<UrlSafe>(input, output),
}
}
/// Dispatch the AVX2 decode kernel to the right alphabet monomorphization.
///
/// # Safety
///
/// The running CPU must support AVX2 (the requirement is forwarded to [`avx2::decode_bulk`]).
#[cfg(target_arch = "x86_64")]
#[inline]
unsafe fn avx2_decode(
kind: SimdKind,
input: &[u8],
quads_end: usize,
output: &mut [u8],
) -> (usize, usize) {
match kind {
// SAFETY: this function's contract guarantees AVX2, which is all the kernel requires.
SimdKind::Standard => avx2::decode_bulk::<Standard>(input, quads_end, output),
SimdKind::UrlSafe => avx2::decode_bulk::<UrlSafe>(input, quads_end, output),
}
}
/// Dispatch the NEON encode kernel to the right alphabet monomorphization.
///
/// # Safety
///
/// The running CPU must support NEON.
#[cfg(target_arch = "aarch64")]
#[inline]
unsafe fn neon_encode(kind: SimdKind, input: &[u8], output: &mut [u8]) -> (usize, usize) {
match kind {
// SAFETY: this function's contract guarantees NEON, which is all the kernel requires.
SimdKind::Standard => neon::encode_bulk::<Standard>(input, output),
SimdKind::UrlSafe => neon::encode_bulk::<UrlSafe>(input, output),
}
}
/// Dispatch the NEON decode kernel to the right alphabet monomorphization.
///
/// # Safety
///
/// The running CPU must support NEON.
#[cfg(target_arch = "aarch64")]
#[inline]
unsafe fn neon_decode(
kind: SimdKind,
input: &[u8],
quads_end: usize,
output: &mut [u8],
) -> (usize, usize) {
match kind {
// SAFETY: this function's contract guarantees NEON, which is all the kernel requires.
SimdKind::Standard => neon::decode_bulk::<Standard>(input, quads_end, output),
SimdKind::UrlSafe => neon::decode_bulk::<UrlSafe>(input, quads_end, output),
}
}
/// Which instruction set an engine dispatches to.
#[cfg(all(feature = "std", any(target_arch = "x86_64", target_arch = "aarch64")))]
#[derive(Clone, Copy, Debug)]
enum Backend {
Scalar,
#[cfg(target_arch = "x86_64")]
Avx2,
#[cfg(target_arch = "aarch64")]
Neon,
}
/// A base64 engine that uses the best SIMD instruction set detected at runtime, falling back to the
/// scalar [`GeneralPurpose`] engine.
///
/// Requires the `std` feature (for runtime CPU-feature detection) and an `x86_64` or `aarch64`
/// target. On other targets, use [`GeneralPurpose`] directly. Only the STANDARD and URL_SAFE
/// alphabets are accelerated, so it is constructed with [`Simd::standard`] / [`Simd::url_safe`].
#[cfg(all(feature = "std", any(target_arch = "x86_64", target_arch = "aarch64")))]
#[derive(Debug, Clone)]
pub struct Simd {
inner: GeneralPurpose,
kind: SimdKind,
backend: Backend,
}
#[cfg(all(feature = "std", any(target_arch = "x86_64", target_arch = "aarch64")))]
impl Simd {
/// Create a `Simd` engine for the STANDARD alphabet, detecting the instruction set once.
#[must_use]
pub fn standard(config: GeneralPurposeConfig) -> Self {
Self::new(SimdKind::Standard, &crate::alphabet::STANDARD, config)
}
/// Create a `Simd` engine for the URL_SAFE alphabet, detecting the instruction set once.
#[must_use]
pub fn url_safe(config: GeneralPurposeConfig) -> Self {
Self::new(SimdKind::UrlSafe, &crate::alphabet::URL_SAFE, config)
}
fn new(
kind: SimdKind,
alphabet: &crate::alphabet::Alphabet,
config: GeneralPurposeConfig,
) -> Self {
#[cfg(target_arch = "x86_64")]
let backend = if std::is_x86_feature_detected!("avx2") {
Backend::Avx2
} else {
Backend::Scalar
};
#[cfg(target_arch = "aarch64")]
let backend = if std::arch::is_aarch64_feature_detected!("neon") {
Backend::Neon
} else {
Backend::Scalar
};
Self {
inner: GeneralPurpose::new(alphabet, config),
kind,
backend,
}
}
}
#[cfg(all(feature = "std", any(target_arch = "x86_64", target_arch = "aarch64")))]
impl Engine for Simd {
type Config = GeneralPurposeConfig;
type DecodeEstimate = GeneralPurposeEstimate;
fn internal_encode(&self, input: &[u8], output: &mut [u8]) -> usize {
let kind = self.kind;
match self.backend {
Backend::Scalar => self.inner.internal_encode(input, output),
#[cfg(target_arch = "x86_64")]
Backend::Avx2 => encode_helper(self.inner.encode_table(), input, output, |i, o| {
// SAFETY: the Avx2 backend is only selected when AVX2 was detected.
unsafe { avx2_encode(kind, i, o) }
}),
#[cfg(target_arch = "aarch64")]
Backend::Neon => encode_helper(self.inner.encode_table(), input, output, |i, o| {
// SAFETY: the Neon backend is only selected when NEON was detected.
unsafe { neon_encode(kind, i, o) }
}),
}
}
fn internal_decoded_len_estimate(&self, input_len: usize) -> Self::DecodeEstimate {
self.inner.internal_decoded_len_estimate(input_len)
}
fn internal_decode(
&self,
input: &[u8],
output: &mut [u8],
estimate: Self::DecodeEstimate,
) -> Result<DecodeMetadata, DecodeSliceError> {
let kind = self.kind;
match self.backend {
Backend::Scalar => self.inner.internal_decode(input, output, estimate),
#[cfg(target_arch = "x86_64")]
Backend::Avx2 => decode_helper(
input,
&estimate,
output,
self.inner.decode_table(),
self.inner.config().decode_allow_trailing_bits(),
self.inner.padding(),
self.inner.config().decode_padding_mode(),
// SAFETY: the Avx2 backend is only selected when AVX2 was detected.
|i, end, o| unsafe { avx2_decode(kind, i, end, o) },
),
#[cfg(target_arch = "aarch64")]
Backend::Neon => decode_helper(
input,
&estimate,
output,
self.inner.decode_table(),
self.inner.config().decode_allow_trailing_bits(),
self.inner.padding(),
self.inner.config().decode_padding_mode(),
// SAFETY: the Neon backend is only selected when NEON was detected.
|i, end, o| unsafe { neon_decode(kind, i, end, o) },
),
}
}
fn config(&self) -> &Self::Config {
self.inner.config()
}
fn padding(&self) -> Symbol {
self.inner.padding()
}
}
/// A base64 engine that unconditionally uses AVX2, without runtime detection.
///
/// This works in `no_std` builds. Because it does not check for AVX2 support, it must only be used
/// on a CPU that has it. Only the STANDARD and URL_SAFE alphabets are accelerated, so it is
/// constructed with the [`Avx2::standard`] / [`Avx2::url_safe`] (checked) or
/// [`Avx2::standard_unchecked`] / [`Avx2::url_safe_unchecked`] constructors.
#[cfg(target_arch = "x86_64")]
#[derive(Debug, Clone)]
pub struct Avx2 {
inner: GeneralPurpose,
kind: SimdKind,
}
#[cfg(target_arch = "x86_64")]
impl Avx2 {
/// Create an `Avx2` engine for the STANDARD alphabet if the running CPU supports AVX2, else
/// `None`.
///
/// Requires the `std` feature for the detection; in `no_std` use [`Avx2::standard_unchecked`].
#[cfg(feature = "std")]
#[must_use]
pub fn standard(config: GeneralPurposeConfig) -> Option<Self> {
if std::is_x86_feature_detected!("avx2") {
// SAFETY: AVX2 support was just verified.
Some(unsafe { Self::standard_unchecked(config) })
} else {
None
}
}
/// Create an `Avx2` engine for the URL_SAFE alphabet if the running CPU supports AVX2, else
/// `None`.
///
/// Requires the `std` feature for the detection; in `no_std` use [`Avx2::url_safe_unchecked`].
#[cfg(feature = "std")]
#[must_use]
pub fn url_safe(config: GeneralPurposeConfig) -> Option<Self> {
if std::is_x86_feature_detected!("avx2") {
// SAFETY: AVX2 support was just verified.
Some(unsafe { Self::url_safe_unchecked(config) })
} else {
None
}
}
/// Create an `Avx2` engine for the STANDARD alphabet without checking for AVX2 support.
///
/// # Safety
///
/// The CPU that will run encode/decode must support AVX2. Using the engine on a CPU without
/// AVX2 is undefined behavior.
#[must_use]
pub const unsafe fn standard_unchecked(config: GeneralPurposeConfig) -> Self {
Self {
inner: GeneralPurpose::new(&crate::alphabet::STANDARD, config),
kind: SimdKind::Standard,
}
}
/// Create an `Avx2` engine for the URL_SAFE alphabet without checking for AVX2 support.
///
/// # Safety
///
/// The CPU that will run encode/decode must support AVX2. Using the engine on a CPU without
/// AVX2 is undefined behavior.
#[must_use]
pub const unsafe fn url_safe_unchecked(config: GeneralPurposeConfig) -> Self {
Self {
inner: GeneralPurpose::new(&crate::alphabet::URL_SAFE, config),
kind: SimdKind::UrlSafe,
}
}
}
#[cfg(target_arch = "x86_64")]
impl Engine for Avx2 {
type Config = GeneralPurposeConfig;
type DecodeEstimate = GeneralPurposeEstimate;
fn internal_encode(&self, input: &[u8], output: &mut [u8]) -> usize {
let kind = self.kind;
encode_helper(self.inner.encode_table(), input, output, |i, o| {
// SAFETY: constructing this engine asserts AVX2 support.
unsafe { avx2_encode(kind, i, o) }
})
}
fn internal_decoded_len_estimate(&self, input_len: usize) -> Self::DecodeEstimate {
self.inner.internal_decoded_len_estimate(input_len)
}
fn internal_decode(
&self,
input: &[u8],
output: &mut [u8],
estimate: Self::DecodeEstimate,
) -> Result<DecodeMetadata, DecodeSliceError> {
let kind = self.kind;
decode_helper(
input,
&estimate,
output,
self.inner.decode_table(),
self.inner.config().decode_allow_trailing_bits(),
self.inner.padding(),
self.inner.config().decode_padding_mode(),
// SAFETY: constructing this engine asserts AVX2 support.
|i, end, o| unsafe { avx2_decode(kind, i, end, o) },
)
}
fn config(&self) -> &Self::Config {
self.inner.config()
}
fn padding(&self) -> Symbol {
self.inner.padding()
}
}
/// A base64 engine that unconditionally uses NEON, without runtime detection.
///
/// This engine is available on aarch64 targets compiled with NEON support and works in `no_std`.
#[cfg(target_arch = "aarch64")]
#[derive(Debug, Clone)]
pub struct Neon {
inner: GeneralPurpose,
kind: SimdKind,
}
#[cfg(target_arch = "aarch64")]
impl Neon {
/// Create a `Neon` engine for the STANDARD alphabet on a target compiled with NEON support.
#[must_use]
pub const fn standard(config: GeneralPurposeConfig) -> Self {
Self {
inner: GeneralPurpose::new(&crate::alphabet::STANDARD, config),
kind: SimdKind::Standard,
}
}
/// Create a `Neon` engine for the URL_SAFE alphabet on a target compiled with NEON support.
#[must_use]
pub const fn url_safe(config: GeneralPurposeConfig) -> Self {
Self {
inner: GeneralPurpose::new(&crate::alphabet::URL_SAFE, config),
kind: SimdKind::UrlSafe,
}
}
}
#[cfg(target_arch = "aarch64")]
impl Engine for Neon {
type Config = GeneralPurposeConfig;
type DecodeEstimate = GeneralPurposeEstimate;
fn internal_encode(&self, input: &[u8], output: &mut [u8]) -> usize {
let kind = self.kind;
encode_helper(self.inner.encode_table(), input, output, |i, o| {
// SAFETY: this module is only compiled for targets with NEON enabled.
unsafe { neon_encode(kind, i, o) }
})
}
fn internal_decoded_len_estimate(&self, input_len: usize) -> Self::DecodeEstimate {
self.inner.internal_decoded_len_estimate(input_len)
}
fn internal_decode(
&self,
input: &[u8],
output: &mut [u8],
estimate: Self::DecodeEstimate,
) -> Result<DecodeMetadata, DecodeSliceError> {
let kind = self.kind;
decode_helper(
input,
&estimate,
output,
self.inner.decode_table(),
self.inner.config().decode_allow_trailing_bits(),
self.inner.padding(),
self.inner.config().decode_padding_mode(),
// SAFETY: this module is only compiled for targets with NEON enabled.
|i, end, o| unsafe { neon_decode(kind, i, end, o) },
)
}
fn config(&self) -> &Self::Config {
self.inner.config()
}
fn padding(&self) -> Symbol {
self.inner.padding()
}
}
+1
-1
{
"git": {
"sha1": "e14400697453bcc85997119b874bc03d9601d0af"
"sha1": "9e9220a4166f628de7c8803289e120ae1e944f78"
},
"path_in_vcs": ""
}

@@ -17,3 +17,3 @@ version: '2.1'

# MSRV
'rust:1.48.0'
'rust:1.71.0'
]

@@ -24,3 +24,3 @@ # a hacky scheme to work around CircleCI's inability to deal with mutable docker tags, forcing us to

'__msrv__', # won't add any other toolchains, just uses what's in the docker image
'1.70.0', # minimum needed to build dev-dependencies
'1.86.0', # minimum needed to build dev-dependencies
'stable',

@@ -40,6 +40,9 @@ 'beta',

- image: << parameters.rust_img >>
retention:
# lower retention since repo activity is pretty bursty
caches: 7d
steps:
- checkout
- restore_cache:
key: project-cache-v5-<< parameters.rust_img >>-<< parameters.toolchain_override >>-{{ checksum "Cargo.toml" }}
key: project-cache-v7-<< parameters.rust_img >>-<< parameters.toolchain_override >>-{{ checksum "Cargo.toml" }}
- run:

@@ -100,3 +103,3 @@ name: Setup toolchain

- run:
name: Add arm toolchain
name: Add arm target
command: rustup target add thumbv6m-none-eabi

@@ -110,4 +113,13 @@ - run:

- run:
# dev dependencies can't build on 1.48.0
name: Add wasm target
command: rustup target add wasm32-unknown-unknown
- run:
name: Build wasm without default features (no_std)
command: cargo build --target wasm32-unknown-unknown --no-default-features
- run:
# dev dependencies can't build on MSRV
name: Run tests
# enable non-runtime-detected simd
environment:
RUSTFLAGS: "-C target-cpu=native"
command: |

@@ -123,2 +135,92 @@ if [[ '<< parameters.toolchain_override >>' != '__msrv__' ]]

- run:
name: Test unsafe simd with miri x64 with stacked borrows
# enable non-runtime-detected simd
environment:
RUSTFLAGS: "-C target-feature=+avx2"
command: |
if [[ '<< parameters.toolchain_override >>' = 'nightly' ]]
then
rustup component add miri
cargo miri test --target x86_64-unknown-linux-gnu miri
fi
- run:
name: Test unsafe simd with miri aarch64 with stacked borrows
# enable non-runtime-detected simd
environment:
RUSTFLAGS: "-C target-feature=+neon"
command: |
if [[ '<< parameters.toolchain_override >>' = 'nightly' ]]
then
cargo miri test --target aarch64-unknown-linux-gnu miri
fi
- run:
name: Test unsafe simd with miri x64 with tree borrows
# enable non-runtime-detected simd
environment:
MIRIFLAGS: "-Zmiri-tree-borrows"
RUSTFLAGS: "-C target-feature=+avx2"
command: |
if [[ '<< parameters.toolchain_override >>' = 'nightly' ]]
then
rustup component add miri
cargo miri test --target x86_64-unknown-linux-gnu miri
fi
- run:
name: Test unsafe simd with miri aarch64 with tree borrows
# enable non-runtime-detected simd
environment:
MIRIFLAGS: "-Zmiri-tree-borrows"
RUSTFLAGS: "-C target-feature=+neon"
command: |
if [[ '<< parameters.toolchain_override >>' = 'nightly' ]]
then
cargo miri test --target aarch64-unknown-linux-gnu miri
fi
- run:
name: Test unsafe simd with miri x64 with strict provenance
# enable non-runtime-detected simd
environment:
MIRIFLAGS: "-Zmiri-strict-provenance"
RUSTFLAGS: "-C target-feature=+avx2"
command: |
if [[ '<< parameters.toolchain_override >>' = 'nightly' ]]
then
rustup component add miri
cargo miri test --target x86_64-unknown-linux-gnu miri
fi
- run:
name: Test unsafe simd with miri aarch64 with strict provenance
# enable non-runtime-detected simd
environment:
MIRIFLAGS: "-Zmiri-strict-provenance"
RUSTFLAGS: "-C target-feature=+neon"
command: |
if [[ '<< parameters.toolchain_override >>' = 'nightly' ]]
then
cargo miri test --target aarch64-unknown-linux-gnu miri
fi
- run:
name: Test unsafe simd with miri x64 with many seeds
# enable non-runtime-detected simd
environment:
MIRIFLAGS: "-Zmiri-many-seeds=0..4"
RUSTFLAGS: "-C target-feature=+avx2"
command: |
if [[ '<< parameters.toolchain_override >>' = 'nightly' ]]
then
rustup component add miri
cargo miri test --target x86_64-unknown-linux-gnu miri
fi
- run:
name: Test unsafe simd with miri aarch64 with many seeds
# enable non-runtime-detected simd
environment:
MIRIFLAGS: "-Zmiri-many-seeds=0..4"
RUSTFLAGS: "-C target-feature=+neon"
command: |
if [[ '<< parameters.toolchain_override >>' = 'nightly' ]]
then
cargo miri test --target aarch64-unknown-linux-gnu miri
fi
- run:
name: Confirm fuzzers can run

@@ -136,3 +238,3 @@ # TERM=dumb prevents cargo fuzz list from printing with color

- save_cache:
key: project-cache-v5-<< parameters.rust_img >>-<< parameters.toolchain_override >>-{{ checksum "Cargo.toml" }}
key: project-cache-v7-<< parameters.rust_img >>-<< parameters.toolchain_override >>-{{ checksum "Cargo.toml" }}
paths:

@@ -142,2 +244,1 @@ # rust docker img doesn't use $HOME/[.cargo,.rustup]

- /usr/local/rustup
- ./target
target/
Cargo.lock
*~

@@ -4,0 +3,0 @@ *.swp

@@ -9,4 +9,5 @@ #[macro_use]

};
use criterion::{black_box, Bencher, BenchmarkId, Criterion, Throughput};
use rand::{Rng, SeedableRng};
use criterion::{Bencher, BenchmarkId, Criterion, Throughput};
use rand::{rngs, RngExt};
use std::hint::black_box;
use std::io::{self, Read, Write};

@@ -56,3 +57,3 @@

let mut buf = vec![0; size];
buf.truncate(0);
buf.clear();

@@ -104,2 +105,34 @@ b.iter(|| {

#[cfg(all(
feature = "simd-unsafe",
feature = "std",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
fn do_encode_bench_slice_simd(b: &mut Bencher, &size: &usize) {
let engine = base64::engine::Simd::standard(base64::engine::general_purpose::PAD);
let mut v: Vec<u8> = Vec::with_capacity(size);
fill(&mut v);
// conservative estimate of encoded size
let mut buf = vec![0; v.len() * 2];
b.iter(|| engine.encode_slice(&v, &mut buf).unwrap());
}
#[cfg(all(
feature = "simd-unsafe",
feature = "std",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
fn do_decode_bench_slice_simd(b: &mut Bencher, &size: &usize) {
let engine = base64::engine::Simd::standard(base64::engine::general_purpose::PAD);
let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
fill(&mut v);
let encoded = engine.encode(&v);
let mut buf = vec![0; size];
b.iter(|| {
engine.decode_slice(&encoded, &mut buf).unwrap();
black_box(&buf);
});
}
fn do_encode_bench_stream(b: &mut Bencher, &size: &usize) {

@@ -147,5 +180,5 @@ let mut v: Vec<u8> = Vec::with_capacity(size);

// weak randomness is plenty; we just want to not be completely friendly to the branch predictor
let mut r = rand::rngs::SmallRng::from_entropy();
let mut r = rand::make_rng::<rngs::SmallRng>();
while v.len() < cap {
v.push(r.gen::<u8>());
v.push(r.random::<u8>());
}

@@ -200,2 +233,13 @@ }

);
#[cfg(all(
feature = "simd-unsafe",
feature = "std",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
group.bench_with_input(
BenchmarkId::new("encode_slice_simd", size),
size,
do_encode_bench_slice_simd,
);
}

@@ -230,2 +274,13 @@

);
#[cfg(all(
feature = "simd-unsafe",
feature = "std",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
group.bench_with_input(
BenchmarkId::new("decode_slice_simd", size),
size,
do_decode_bench_slice_simd,
);
}

@@ -232,0 +287,0 @@

+354
-893

@@ -6,193 +6,79 @@ # This file is automatically @generated by Cargo.

[[package]]
name = "anes"
version = "0.1.6"
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "async-attributes"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"quote",
"syn 1.0.109",
"memchr",
]
[[package]]
name = "async-channel"
version = "1.9.0"
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
"event-listener 2.5.3",
"futures-core",
]
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "async-channel"
version = "2.2.0"
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3"
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
"concurrent-queue",
"event-listener 5.2.0",
"event-listener-strategy 0.5.0",
"futures-core",
"pin-project-lite",
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "async-executor"
version = "1.8.0"
name = "anstyle"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c"
dependencies = [
"async-lock 3.3.0",
"async-task",
"concurrent-queue",
"fastrand 2.0.1",
"futures-lite 2.2.0",
"slab",
]
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "async-global-executor"
version = "2.4.1"
name = "anstyle-parse"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
"async-channel 2.2.0",
"async-executor",
"async-io 2.3.1",
"async-lock 3.3.0",
"blocking",
"futures-lite 2.2.0",
"once_cell",
"utf8parse",
]
[[package]]
name = "async-io"
version = "1.13.0"
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"async-lock 2.8.0",
"autocfg",
"cfg-if",
"concurrent-queue",
"futures-lite 1.13.0",
"log",
"parking",
"polling 2.8.0",
"rustix 0.37.27",
"slab",
"socket2",
"waker-fn",
"windows-sys",
]
[[package]]
name = "async-io"
version = "2.3.1"
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"async-lock 3.3.0",
"cfg-if",
"concurrent-queue",
"futures-io",
"futures-lite 2.2.0",
"parking",
"polling 3.4.0",
"rustix 0.38.9",
"slab",
"tracing",
"windows-sys 0.52.0",
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "async-lock"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
dependencies = [
"event-listener 2.5.3",
]
[[package]]
name = "async-lock"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b"
dependencies = [
"event-listener 4.0.3",
"event-listener-strategy 0.4.0",
"pin-project-lite",
]
[[package]]
name = "async-std"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
dependencies = [
"async-attributes",
"async-channel 1.9.0",
"async-global-executor",
"async-io 1.13.0",
"async-lock 2.8.0",
"crossbeam-utils",
"futures-channel",
"futures-core",
"futures-io",
"futures-lite 1.13.0",
"gloo-timers",
"kv-log-macro",
"log",
"memchr",
"once_cell",
"pin-project-lite",
"pin-utils",
"slab",
"wasm-bindgen-futures",
]
[[package]]
name = "async-task"
version = "4.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "base64"
version = "0.22.1"
version = "0.23.0"
dependencies = [
"clap",
"criterion",
"once_cell",
"rand",
"rand 0.10.2",
"rstest",

@@ -204,34 +90,6 @@ "rstest_reuse",

[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "blocking"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
dependencies = [
"async-channel 2.2.0",
"async-lock 3.3.0",
"async-task",
"fastrand 2.0.1",
"futures-io",
"futures-lite 2.2.0",
"piper",
"tracing",
]
[[package]]
name = "bumpalo"
version = "3.15.3"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"

@@ -246,7 +104,18 @@ [[package]]

name = "cfg-if"
version = "1.0.0"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chacha20"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
dependencies = [
"cfg-if",
"cpufeatures",
"rand_core 0.10.1",
]
[[package]]
name = "ciborium"

@@ -280,15 +149,20 @@ version = "0.2.2"

name = "clap"
version = "3.2.25"
version = "4.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7"
dependencies = [
"atty",
"bitflags 1.3.2",
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"indexmap",
"once_cell",
"strsim",
"termcolor",
"textwrap",
]

@@ -298,11 +172,10 @@

name = "clap_derive"
version = "3.2.25"
version = "4.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008"
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 3.0.3",
]

@@ -312,16 +185,19 @@

name = "clap_lex"
version = "0.2.4"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "concurrent-queue"
version = "2.4.0"
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"crossbeam-utils",
"libc",
]

@@ -331,8 +207,7 @@

name = "criterion"
version = "0.4.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb"
checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928"
dependencies = [
"anes",
"atty",
"cast",

@@ -343,3 +218,2 @@ "ciborium",

"itertools",
"lazy_static",
"num-traits",

@@ -351,3 +225,2 @@ "oorandom",

"serde",
"serde_derive",
"serde_json",

@@ -360,5 +233,5 @@ "tinytemplate",

name = "criterion-plot"
version = "0.5.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338"
dependencies = [

@@ -371,5 +244,5 @@ "cast",

name = "crossbeam-deque"
version = "0.8.5"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
dependencies = [

@@ -382,5 +255,5 @@ "crossbeam-epoch",

name = "crossbeam-epoch"
version = "0.9.18"
version = "0.9.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
dependencies = [

@@ -392,221 +265,63 @@ "crossbeam-utils",

name = "crossbeam-utils"
version = "0.8.19"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]]
name = "crunchy"
version = "0.2.2"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "ctor"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
dependencies = [
"quote",
"syn 1.0.109",
]
[[package]]
name = "either"
version = "1.10.0"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
[[package]]
name = "errno"
version = "0.3.8"
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "event-listener"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
dependencies = [
"event-listener 4.0.3",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291"
dependencies = [
"event-listener 5.2.0",
"pin-project-lite",
]
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]]
name = "fastrand"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "futures"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.30"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
[[package]]
name = "futures-executor"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-lite"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
"fastrand 1.9.0",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]]
name = "futures-lite"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba"
dependencies = [
"fastrand 2.0.1",
"futures-core",
"futures-io",
"parking",
"pin-project-lite",
]
[[package]]
name = "futures-macro"
version = "0.3.30"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.119",
]
[[package]]
name = "futures-sink"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
version = "0.3.30"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
[[package]]
name = "futures-timer"
version = "3.0.3"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968"
[[package]]
name = "futures-util"
version = "0.3.30"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",

@@ -617,5 +332,5 @@ ]

name = "getrandom"
version = "0.2.12"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [

@@ -628,21 +343,28 @@ "cfg-if",

[[package]]
name = "gloo-timers"
version = "0.2.6"
name = "getrandom"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"futures-channel",
"futures-core",
"js-sys",
"wasm-bindgen",
"cfg-if",
"libc",
"r-efi",
"rand_core 0.10.1",
]
[[package]]
name = "glob"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
[[package]]
name = "half"
version = "2.4.0"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e"
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
"cfg-if",
"crunchy",
"zerocopy",
]

@@ -652,34 +374,19 @@

name = "hashbrown"
version = "0.12.3"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "heck"
version = "0.4.1"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "indexmap"
version = "1.9.3"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"autocfg",
"equivalent",
"hashbrown",

@@ -689,26 +396,12 @@ ]

[[package]]
name = "instant"
version = "0.1.12"
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "io-lifetimes"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi 0.3.9",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "itertools"
version = "0.10.5"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [

@@ -720,12 +413,14 @@ "either",

name = "itoa"
version = "1.0.10"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.68"
version = "0.3.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
dependencies = [
"cfg-if",
"futures-util",
"wasm-bindgen",

@@ -735,55 +430,18 @@ ]

[[package]]
name = "kv-log-macro"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
dependencies = [
"log",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.153"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
"value-bag",
]
[[package]]
name = "memchr"
version = "2.7.1"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "num-traits"
version = "0.2.18"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [

@@ -795,52 +453,29 @@ "autocfg",

name = "once_cell"
version = "1.19.0"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "oorandom"
version = "11.1.3"
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "os_str_bytes"
version = "6.6.1"
name = "oorandom"
version = "11.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
[[package]]
name = "parking"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
[[package]]
name = "pin-project-lite"
version = "0.2.13"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "piper"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
dependencies = [
"atomic-waker",
"fastrand 2.0.1",
"futures-io",
]
[[package]]
name = "plotters"
version = "0.3.5"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
dependencies = [

@@ -856,11 +491,11 @@ "num-traits",

name = "plotters-backend"
version = "0.3.5"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
[[package]]
name = "plotters-svg"
version = "0.3.5"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
dependencies = [

@@ -871,77 +506,52 @@ "plotters-backend",

[[package]]
name = "polling"
version = "2.8.0"
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"autocfg",
"bitflags 1.3.2",
"cfg-if",
"concurrent-queue",
"libc",
"log",
"pin-project-lite",
"windows-sys 0.48.0",
"zerocopy",
]
[[package]]
name = "polling"
version = "3.4.0"
name = "proc-macro-crate"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30054e72317ab98eddd8561db0f6524df3367636884b7b21b703e4b280a84a14"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [
"cfg-if",
"concurrent-queue",
"pin-project-lite",
"rustix 0.38.9",
"tracing",
"windows-sys 0.52.0",
"toml_edit",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
"unicode-ident",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.78"
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "quote"
version = "1.0.35"
name = "rand"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
dependencies = [
"proc-macro2",
"libc",
"rand_chacha",
"rand_core 0.6.4",
]

@@ -951,9 +561,9 @@

name = "rand"
version = "0.8.5"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"chacha20",
"getrandom 0.4.3",
"rand_core 0.10.1",
]

@@ -968,3 +578,3 @@

"ppv-lite86",
"rand_core",
"rand_core 0.6.4",
]

@@ -978,10 +588,16 @@

dependencies = [
"getrandom",
"getrandom 0.2.17",
]
[[package]]
name = "rand_core"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]]
name = "rayon"
version = "1.9.0"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd"
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
dependencies = [

@@ -994,5 +610,5 @@ "either",

name = "rayon-core"
version = "1.12.1"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [

@@ -1005,6 +621,9 @@ "crossbeam-deque",

name = "regex"
version = "1.8.4"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",

@@ -1014,18 +633,33 @@ ]

[[package]]
name = "regex-automata"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.7.5"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "relative-path"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
[[package]]
name = "rstest"
version = "0.13.0"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b939295f93cb1d12bc1a83cf9ee963199b133fb8a79832dd51b68bb9f59a04dc"
checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49"
dependencies = [
"async-std",
"futures",
"futures-timer",
"futures-util",
"rstest_macros",
"rustc_version",
]

@@ -1035,11 +669,16 @@

name = "rstest_macros"
version = "0.13.0"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f78aba848123782ba59340928ec7d876ebe745aa0365d6af8a630f19a5c16116"
checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0"
dependencies = [
"cfg-if",
"glob",
"proc-macro-crate",
"proc-macro2",
"quote",
"regex",
"relative-path",
"rustc_version",
"syn 1.0.109",
"syn 2.0.119",
"unicode-ident",
]

@@ -1049,10 +688,9 @@

name = "rstest_reuse"
version = "0.6.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88530b681abe67924d42cca181d070e3ac20e0740569441a9e35a7cedd2b34a4"
checksum = "b3a8fb4672e840a587a66fc577a5491375df51ddb88f2a2c2a792598c326fe14"
dependencies = [
"quote",
"rand",
"rustc_version",
"syn 2.0.52",
"rand 0.8.7",
"syn 2.0.119",
]

@@ -1062,5 +700,5 @@

name = "rustc_version"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [

@@ -1071,41 +709,8 @@ "semver",

[[package]]
name = "rustix"
version = "0.37.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
dependencies = [
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys 0.3.8",
"windows-sys 0.48.0",
]
[[package]]
name = "rustix"
version = "0.38.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49"
dependencies = [
"bitflags 2.4.2",
"errno",
"libc",
"linux-raw-sys 0.4.13",
"windows-sys 0.48.0",
]
[[package]]
name = "rustversion"
version = "1.0.14"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "ryu"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "same-file"

@@ -1121,12 +726,13 @@ version = "1.0.6"

name = "semver"
version = "1.0.22"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "serde"
version = "1.0.197"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
"serde_derive",

@@ -1136,10 +742,19 @@ ]

[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.197"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 3.0.3",
]

@@ -1149,9 +764,11 @@

name = "serde_json"
version = "1.0.114"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"itoa",
"ryu",
"memchr",
"serde",
"serde_core",
"zmij",
]

@@ -1161,30 +778,17 @@

name = "slab"
version = "0.4.9"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "socket2"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "strsim"
version = "0.10.0"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.25.0"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
dependencies = [

@@ -1196,5 +800,5 @@ "strum_macros",

name = "strum_macros"
version = "0.25.3"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
dependencies = [

@@ -1204,4 +808,3 @@ "heck",

"quote",
"rustversion",
"syn 2.0.52",
"syn 2.0.119",
]

@@ -1211,5 +814,5 @@

name = "syn"
version = "1.0.109"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [

@@ -1223,5 +826,5 @@ "proc-macro2",

name = "syn"
version = "2.0.52"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [

@@ -1234,17 +837,2 @@ "proc-macro2",

[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
[[package]]
name = "tinytemplate"

@@ -1260,44 +848,42 @@ version = "1.2.1"

[[package]]
name = "tracing"
version = "0.1.40"
name = "toml_datetime"
version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
dependencies = [
"pin-project-lite",
"tracing-core",
"serde_core",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
name = "toml_edit"
version = "0.25.13+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
dependencies = [
"indexmap",
"toml_datetime",
"toml_parser",
"winnow",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
name = "toml_parser"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "value-bag"
version = "1.0.0-alpha.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [
"ctor",
"version_check",
"winnow",
]
[[package]]
name = "version_check"
version = "0.9.4"
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "waker-fn"
version = "1.1.1"
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"

@@ -1316,28 +902,16 @@ [[package]]

name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasm-bindgen"
version = "0.2.91"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.52",
"wasm-bindgen-shared",

@@ -1347,18 +921,6 @@ ]

[[package]]
name = "wasm-bindgen-futures"
version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.91"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
dependencies = [

@@ -1371,10 +933,10 @@ "quote",

name = "wasm-bindgen-macro-support"
version = "0.2.91"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn 2.0.52",
"wasm-bindgen-backend",
"syn 2.0.119",
"wasm-bindgen-shared",

@@ -1385,11 +947,14 @@ ]

name = "wasm-bindgen-shared"
version = "0.2.91"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.68"
version = "0.3.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446"
checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141"
dependencies = [

@@ -1401,162 +966,58 @@ "js-sys",

[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.6"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"winapi",
"windows-sys",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.48.0"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-targets 0.48.5",
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
name = "winnow"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
dependencies = [
"windows-targets 0.52.4",
"memchr",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
name = "zerocopy"
version = "0.8.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
"zerocopy-derive",
]
[[package]]
name = "windows-targets"
version = "0.52.4"
name = "zerocopy-derive"
version = "0.8.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb"
dependencies = [
"windows_aarch64_gnullvm 0.52.4",
"windows_aarch64_msvc 0.52.4",
"windows_i686_gnu 0.52.4",
"windows_i686_msvc 0.52.4",
"windows_x86_64_gnu 0.52.4",
"windows_x86_64_gnullvm 0.52.4",
"windows_x86_64_msvc 0.52.4",
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"

@@ -13,7 +13,13 @@ # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO

[package]
edition = "2018"
rust-version = "1.48.0"
edition = "2021"
rust-version = "1.71.0"
name = "base64"
version = "0.22.1"
version = "0.23.0"
authors = ["Marshall Pierce <marshall@mpierce.org>"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "encodes and decodes base64 as bytes or utf8"

@@ -36,18 +42,28 @@ documentation = "https://docs.rs/base64"

[profile.bench]
debug = 2
[features]
alloc = []
default = [
"std",
"simd-unsafe",
]
simd-unsafe = []
std = ["alloc"]
[profile.test]
opt-level = 3
[lib]
name = "base64"
path = "src/lib.rs"
[[example]]
name = "base64"
path = "examples/base64.rs"
required-features = ["std"]
[[test]]
name = "tests"
name = "encode"
path = "tests/encode.rs"
required-features = ["alloc"]
[[test]]
name = "encode"
name = "tests"
path = "tests/tests.rs"
required-features = ["alloc"]

@@ -57,2 +73,3 @@

name = "benchmarks"
path = "benches/benchmarks.rs"
harness = false

@@ -62,28 +79,25 @@ required-features = ["std"]

[dev-dependencies.clap]
version = "3.2.25"
version = "4.6.4"
features = ["derive"]
[dev-dependencies.criterion]
version = "0.4.0"
version = "0.7.0"
[dev-dependencies.once_cell]
version = "1"
[dev-dependencies.rand]
version = "0.8.5"
features = ["small_rng"]
version = "0.10.2"
[dev-dependencies.rstest]
version = "0.13.0"
version = "0.26.1"
[dev-dependencies.rstest_reuse]
version = "0.6.0"
version = "0.7.0"
[dev-dependencies.strum]
version = "0.25"
version = "0.28.0"
features = ["derive"]
[features]
alloc = []
default = ["std"]
std = ["alloc"]
[profile.bench]
debug = 2
[profile.test]
opt-level = 3

@@ -1,1 +0,1 @@

msrv = "1.48.0"
msrv = "1.71.0"

@@ -33,3 +33,3 @@ use std::fs::File;

/// The file to encode or decode.
#[structopt(name = "FILE", parse(from_os_str))]
#[structopt(name = "FILE")]
file: Option<PathBuf>,

@@ -36,0 +36,0 @@ }

# [base64](https://crates.io/crates/base64)
[![](https://img.shields.io/crates/v/base64.svg)](https://crates.io/crates/base64) [![Docs](https://docs.rs/base64/badge.svg)](https://docs.rs/base64) [![CircleCI](https://circleci.com/gh/marshallpierce/rust-base64/tree/master.svg?style=shield)](https://circleci.com/gh/marshallpierce/rust-base64/tree/master) [![codecov](https://codecov.io/gh/marshallpierce/rust-base64/branch/master/graph/badge.svg)](https://codecov.io/gh/marshallpierce/rust-base64) [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
[![](https://img.shields.io/crates/v/base64.svg)](https://crates.io/crates/base64) [![Docs](https://docs.rs/base64/badge.svg)](https://docs.rs/base64) [![CircleCI](https://circleci.com/gh/marshallpierce/rust-base64/tree/master.svg?style=shield)](https://circleci.com/gh/marshallpierce/rust-base64/tree/master) [![codecov](https://codecov.io/gh/marshallpierce/rust-base64/branch/master/graph/badge.svg)](https://codecov.io/gh/marshallpierce/rust-base64)

@@ -66,3 +66,3 @@ <a href="https://www.jetbrains.com/?from=rust-base64"><img src="/icon_CLion.svg" height="40px"/></a>

The minimum supported Rust version is 1.48.0.
The minimum supported Rust version is 1.71.0.

@@ -93,2 +93,27 @@ # Contributing

## SIMD acceleration
The default-on `simd-unsafe` feature enables SIMD-accelerated engines for the standard and
URL-safe alphabets, which are several times faster than the scalar `GeneralPurpose` engine. It is
the only feature that uses `unsafe`; without it the crate is `#![forbid(unsafe_code)]`.
The `Simd` engine detects the best available instruction set (AVX2 on `x86_64`, NEON on `aarch64`) at
runtime and falls back to the scalar engine, and needs the `std` feature. The `Avx2` and `Neon`
engines target one instruction set without runtime detection, so they can be used in `no_std` builds
when the target is known to support the instructions.
### Testing SIMD
Testing SIMD directly requires having all of the necessary hardware available. Fortunately, the instructions we use are
also provided by Miri, so we can check for UB and proper logic all at once on any system. Here, this is filtering for
tests with `miri` in the name as those are written to be acceptably slow under Miri's overhead, but any test should work
(eventually).
```
RUSTFLAGS="-C target-feature=+avx2" cargo +nightly miri \
test --target x86_64-unknown-linux-gnu miri
RUSTFLAGS="-C target-feature=+neon" cargo +nightly miri \
test --target aarch64-unknown-linux-gnu miri
```
## Profiling

@@ -156,2 +181,1 @@

This project is dual-licensed under MIT and Apache 2.0.

@@ -0,1 +1,12 @@

# 0.23.0
- Added more consts for preconfigured configs and engines
- Make DecodeError::InvalidLastSymbol more clear by including the decoded value
- Added SIMD-accelerated engines behind the default-on `simd-unsafe` feature: `Simd` picks the best
instruction set at runtime (AVX2 on `x86_64`, NEON on `aarch64`) and falls back to the scalar
`GeneralPurpose` engine, while `Avx2` and `Neon` target one instruction set with no runtime
detection and work in `no_std`. The engines support the standard and URL-safe alphabets.
- Update MSRV to 1.71.0
- Add support for custom padding symbols
# 0.22.1

@@ -2,0 +13,0 @@

//! Provides [Alphabet] and constants for alphabets commonly used in the wild.
use crate::PAD_BYTE;
use core::{convert, fmt};
use core::{array, convert, fmt};
#[cfg(any(feature = "std", test))]
use std::error;
const ALPHABET_SIZE: usize = 64;
/// Unsurprisingly, there are 64 symbols in a Base64 alphabet.
const ALPHABET_LEN: usize = 64;
/// Pad symbol for non-weird alphabets.
pub(crate) const PADDING_SYMBOL: Symbol = Symbol(b'=');
/// An alphabet defines the 64 ASCII characters (symbols) used for base64.

@@ -44,15 +47,14 @@ ///

/// ```
/// use base64::{
/// alphabet::Alphabet,
/// engine::{general_purpose::GeneralPurpose, GeneralPurposeConfig},
/// };
/// use once_cell::sync::Lazy;
/// use base64::alphabet::Alphabet;
/// use std::sync::LazyLock;
///
/// static CUSTOM: Lazy<Alphabet> = Lazy::new(||
/// static CUSTOM: LazyLock<Alphabet> = LazyLock::new(||
/// Alphabet::new("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/").unwrap()
/// );
/// ```
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Eq, PartialEq)]
pub struct Alphabet {
pub(crate) symbols: [u8; ALPHABET_SIZE],
/// All bytes are valid symbols, but left as u8 to allow `.as_str()` to work.
pub(crate) symbols: [u8; ALPHABET_LEN],
pub(crate) padding: Symbol,
}

@@ -63,4 +65,4 @@

/// Used only for known-valid strings.
const fn from_str_unchecked(alphabet: &str) -> Self {
let mut symbols = [0_u8; ALPHABET_SIZE];
const fn from_str_unchecked(alphabet: &str, padding: Symbol) -> Self {
let mut symbols = [0_u8; ALPHABET_LEN];
let source_bytes = alphabet.as_bytes();

@@ -70,3 +72,3 @@

let mut index = 0;
while index < ALPHABET_SIZE {
while index < ALPHABET_LEN {
symbols[index] = source_bytes[index];

@@ -76,11 +78,27 @@ index += 1;

Self { symbols }
Self { symbols, padding }
}
/// Create an `Alphabet` from a string of 64 unique printable ASCII bytes.
/// Create an `Alphabet` from a string of 64 unique printable ASCII bytes with `=` as the
/// padding symbol.
///
/// The `=` byte is not allowed as it is used for padding.
/// The padding symbol `=` is not allowed in the alphabet.
///
/// See [`Self::new_with_padding`] if a non-default padding symbol is needed.
pub const fn new(alphabet: &str) -> Result<Self, ParseAlphabetError> {
Self::new_with_padding(alphabet, PADDING_SYMBOL)
}
/// Create an `Alphabet` from a string of 64 unique printable ASCII bytes, with a custom
/// padding symbol.
///
/// The padding symbol must not appear in the alphabet.
///
/// This is meant for strange alphabets that don't use `=` as the padding symbol.
pub const fn new_with_padding(
alphabet: &str,
padding: Symbol,
) -> Result<Self, ParseAlphabetError> {
let bytes = alphabet.as_bytes();
if bytes.len() != ALPHABET_SIZE {
if bytes.len() != ALPHABET_LEN {
return Err(ParseAlphabetError::InvalidLength);

@@ -91,12 +109,9 @@ }

let mut index = 0;
while index < ALPHABET_SIZE {
while index < ALPHABET_LEN {
let byte = bytes[index];
// must be ascii printable. 127 (DEL) is commonly considered printable
// for some reason but clearly unsuitable for base64.
if !(byte >= 32_u8 && byte <= 126_u8) {
if !is_valid_b64_symbol(byte) {
return Err(ParseAlphabetError::UnprintableByte(byte));
}
// = is assumed to be padding, so cannot be used as a symbol
if byte == PAD_BYTE {
if byte == padding.as_u8() {
return Err(ParseAlphabetError::ReservedByte(byte));

@@ -110,11 +125,4 @@ }

let mut probe_index = 0;
while probe_index < ALPHABET_SIZE {
if probe_index == index {
probe_index += 1;
continue;
}
let probe_byte = bytes[probe_index];
if byte == probe_byte {
while probe_index < ALPHABET_LEN {
if probe_index != index && byte == bytes[probe_index] {
return Err(ParseAlphabetError::DuplicatedByte(byte));

@@ -130,11 +138,83 @@ }

Ok(Self::from_str_unchecked(alphabet))
Ok(Self::from_str_unchecked(alphabet, padding))
}
/// Create a `&str` from the symbols in the `Alphabet`
/// A `&str` containing the symbols in the `Alphabet` (excluding padding)
#[must_use]
pub fn as_str(&self) -> &str {
core::str::from_utf8(&self.symbols).unwrap()
}
/// The 64 symbols of the alphabet (excluding padding).
pub fn symbols(&self) -> [Symbol; ALPHABET_LEN] {
array::from_fn(|i| {
// safe to construct Symbol since all symbol bytes have already been checked
Symbol(self.symbols[i])
})
}
/// The symbol used for padding.
pub fn padding(&self) -> Symbol {
self.padding
}
}
impl fmt::Debug for Alphabet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Alphabet {{ symbols: {:?}, padding: '{:?}' }}",
self.as_str(),
self.padding
)
}
}
/// An ASCII printable byte suitable for use as a base64 symbol in an alphabet or as custom padding.
///
/// This doesn't mean that a particular symbol is used in any particular alphabet, just that it
/// could be used in one.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Symbol(u8);
impl Symbol {
/// Returns `Some` if `symbol` is a valid printable ASCII symbol, otherwise `None`.
pub const fn new(symbol: u8) -> Option<Self> {
if is_valid_b64_symbol(symbol) {
Some(Self(symbol))
} else {
None
}
}
/// Returns the symbol as an ASCII byte.
pub const fn as_u8(&self) -> u8 {
self.0
}
/// Returns the symbol as a char.
pub fn as_char(&self) -> char {
// ascii u8 is the same as the code point, conveniently
char::from(self.0)
}
}
impl From<Symbol> for u8 {
fn from(value: Symbol) -> Self {
value.0
}
}
impl fmt::Debug for Symbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_char())
}
}
/// Must be ascii printable. 127 (DEL) is commonly considered printable
/// for some reason but clearly unsuitable for base64.
pub(crate) const fn is_valid_b64_symbol(byte: u8) -> bool {
byte >= 32_u8 && byte <= 126_u8
}
impl convert::TryFrom<&str> for Alphabet {

@@ -157,3 +237,3 @@ type Error = ParseAlphabetError;

UnprintableByte(u8),
/// `=` cannot be used
/// Alphabet cannot contain the pad symbol (`=` by default)
ReservedByte(u8),

@@ -181,2 +261,3 @@ }

"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
PADDING_SYMBOL,
);

@@ -189,2 +270,3 @@

"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
PADDING_SYMBOL,
);

@@ -197,2 +279,3 @@

"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
PADDING_SYMBOL,
);

@@ -203,2 +286,3 @@

"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
PADDING_SYMBOL,
);

@@ -211,5 +295,6 @@

"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
PADDING_SYMBOL,
);
/// The alphabet used in BinHex 4.0 files.
/// The alphabet used in `BinHex` 4.0 files.
///

@@ -219,2 +304,3 @@ /// See [BinHex 4.0 Definition](http://files.stairways.com/other/binhex-40-specs-info.txt)

"!\"#$%&'()*+,-012345689@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdefhijklmpqr",
PADDING_SYMBOL,
);

@@ -299,2 +385,35 @@

}
#[test]
fn symbol_matches_char_for_all_valid_symbols() {
for symbol in (0..=u8::MAX).filter_map(Symbol::new) {
// treat the byte as UTF-8
let bytes = &[symbol.as_u8()];
let s = std::str::from_utf8(bytes).unwrap();
assert_eq!(1, s.chars().count());
let char = s.chars().next().unwrap();
assert_eq!(char, symbol.as_char());
}
}
#[test]
fn alphabet_debug() {
assert_eq!(
r##"Alphabet { symbols: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", padding: '=' }"##,
format!("{STANDARD:?}")
);
}
#[test]
fn alphabet_symbols() {
assert_eq!(
STANDARD.as_str(),
STANDARD
.symbols()
.iter()
.map(|s| s.as_char())
.collect::<String>()
);
}
}

@@ -10,3 +10,3 @@ use crate::{

/// The output mechanism for ChunkedEncoder's encoded bytes.
/// The output mechanism for `ChunkedEncoder`'s encoded bytes.
pub trait Sink {

@@ -41,3 +41,3 @@ type Error;

// Pad output to multiple of four bytes if required by config.
len += add_padding(len, &mut buf[len..]);
len += add_padding(len, self.engine.padding(), &mut buf[len..]);
}

@@ -59,3 +59,3 @@ sink.write_encoded_bytes(&buf[..len])?;

impl<'a> StringSink<'a> {
pub(crate) fn new(s: &mut String) -> StringSink {
pub(crate) fn new(s: &mut String) -> StringSink<'_> {
StringSink { string: s }

@@ -78,7 +78,2 @@ }

pub mod tests {
use rand::{
distributions::{Distribution, Uniform},
Rng, SeedableRng,
};
use crate::{

@@ -89,2 +84,4 @@ alphabet::STANDARD,

};
use rand::distr::{Distribution, Uniform};
use rand::{rngs, RngExt};

@@ -128,4 +125,4 @@ use super::*;

let mut output_buf = String::new();
let mut rng = rand::rngs::SmallRng::from_entropy();
let input_len_range = Uniform::new(1, 10_000);
let mut rng = rand::make_rng::<rngs::SmallRng>();
let input_len_range = Uniform::new(1, 10_000).unwrap();

@@ -138,3 +135,3 @@ for _ in 0..20_000 {

for _ in 0..buf_len {
input_buf.push(rng.gen());
input_buf.push(rng.random());
}

@@ -141,0 +138,0 @@

@@ -9,3 +9,3 @@ use crate::engine::{general_purpose::STANDARD, DecodeEstimate, Engine};

/// Errors that can occur while decoding.
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, PartialEq, Eq)]
pub enum DecodeError {

@@ -25,5 +25,19 @@ /// An invalid byte was found in the input. The offset and offending byte are provided.

/// This is indicative of corrupted or truncated Base64.
/// Unlike [DecodeError::InvalidByte], which reports symbols that aren't in the alphabet,
/// Unlike [`DecodeError::InvalidByte`], which reports symbols that aren't in the alphabet,
/// this error is for symbols that are in the alphabet but represent nonsensical encodings.
InvalidLastSymbol(usize, u8),
///
/// See [`crate::engine::GeneralPurposeConfig::with_decode_allow_trailing_bits`] to control
/// whether to detect this encoding error and produce this variant.
InvalidLastSymbol {
/// Offset in the input
offset: usize,
/// The offending symbol
symbol: u8,
/// The bits the symbol corresponds to.
///
/// Since this error is being reported, this value has high bits erroneously set.
/// For a 2-symbol suffix, only the first 2 bits may be set (6 + 2 = 8 bits,
/// 1 byte), and for a 3 symbol, only the first 4 (6 + 6 + 4 = 16, 2 bytes).
symbol_value: u8,
},
/// The nature of the padding was not as configured: absent or incorrect when it must be

@@ -41,4 +55,22 @@ /// canonical, or present when it must be absent, etc.

Self::InvalidLength(len) => write!(f, "Invalid input length: {}", len),
Self::InvalidLastSymbol(index, byte) => {
write!(f, "Invalid last symbol {}, offset {}.", byte, index)
Self::InvalidLastSymbol {
offset,
symbol,
symbol_value,
} => {
write!(
f,
"Invalid last symbol {:#4x} ('{}') at offset {}, decoded as {:#010b}.",
symbol,
// To have been decoded at all, it must have been ascii, but rather than have a
// panicking code path, replacement char seems reasonable.
// Can't use `char::from_u32` as that's 1.52+, so we make a 1-byte str.
core::str::from_utf8(&[symbol])
.ok()
.and_then(|s| s.chars().next())
// associated const is also 1.52+
.unwrap_or(core::char::REPLACEMENT_CHARACTER),
offset,
symbol_value
)
}

@@ -50,2 +82,9 @@ Self::InvalidPadding => write!(f, "Invalid padding"),

impl fmt::Debug for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// 1.48.0 can't handle {self}
write!(f, "{}", self)
}
}
#[cfg(any(feature = "std", test))]

@@ -57,3 +96,3 @@ impl error::Error for DecodeError {}

pub enum DecodeSliceError {
/// A [DecodeError] occurred
/// A [`DecodeError`] occurred
DecodeError(DecodeError),

@@ -91,3 +130,3 @@ /// The provided slice is too small.

///
/// See [Engine::decode].
/// See [`Engine::decode`].
#[deprecated(since = "0.21.0", note = "Use Engine::decode")]

@@ -101,3 +140,3 @@ #[cfg(any(feature = "alloc", test))]

///
/// See [Engine::decode].
/// See [`Engine::decode`].
///Returns a `Result` containing a `Vec<u8>`.

@@ -115,3 +154,3 @@ #[deprecated(since = "0.21.0", note = "Use Engine::decode")]

///
/// See [Engine::decode_vec].
/// See [`Engine::decode_vec`].
#[cfg(any(feature = "alloc", test))]

@@ -129,3 +168,3 @@ #[deprecated(since = "0.21.0", note = "Use Engine::decode_vec")]

///
/// See [Engine::decode_slice].
/// See [`Engine::decode_slice`].
#[deprecated(since = "0.21.0", note = "Use Engine::decode_slice")]

@@ -158,2 +197,3 @@ pub fn decode_engine_slice<E: Engine, T: AsRef<[u8]>>(

/// ```
#[must_use]
pub fn decoded_len_estimate(encoded_len: usize) -> usize {

@@ -170,9 +210,7 @@ STANDARD

alphabet,
engine::{general_purpose, Config, GeneralPurpose},
engine::{general_purpose, GeneralPurpose},
tests::{assert_encode_sanity, random_engine},
};
use rand::{
distributions::{Distribution, Uniform},
Rng, SeedableRng,
};
use rand::distr::{Distribution, Uniform};
use rand::{rngs, RngExt};

@@ -187,6 +225,6 @@ #[test]

let prefix_len_range = Uniform::new(0, 1000);
let input_len_range = Uniform::new(0, 1000);
let prefix_len_range = Uniform::new(0, 1000).unwrap();
let input_len_range = Uniform::new(0, 1000).unwrap();
let mut rng = rand::rngs::SmallRng::from_entropy();
let mut rng = rand::make_rng::<rngs::SmallRng>();

@@ -203,3 +241,3 @@ for _ in 0..10_000 {

for _ in 0..input_len {
orig_data.push(rng.gen());
orig_data.push(rng.random());
}

@@ -209,3 +247,3 @@

engine.encode_string(&orig_data, &mut encoded_data);
assert_encode_sanity(&encoded_data, engine.config().encode_padding(), input_len);
assert_encode_sanity(&encoded_data, &engine, input_len);

@@ -216,3 +254,3 @@ let prefix_len = prefix_len_range.sample(&mut rng);

for _ in 0..prefix_len {
prefix.push(rng.gen());
prefix.push(rng.random());
}

@@ -301,2 +339,16 @@

#[test]
fn invalid_last_symbol_debug() {
let err = DecodeError::InvalidLastSymbol {
offset: 100,
symbol: b'W',
symbol_value: 0x16,
};
assert_eq!(
"Invalid last symbol 0x57 ('W') at offset 100, decoded as 0b00010110.",
format!("{:?}", err)
);
}
fn do_decode_slice_doesnt_clobber_existing_prefix_or_suffix<

@@ -312,5 +364,5 @@ F: Fn(&GeneralPurpose, &[u8], &mut [u8]) -> usize,

let input_len_range = Uniform::new(0, 1000);
let input_len_range = Uniform::new(0, 1000).unwrap();
let mut rng = rand::rngs::SmallRng::from_entropy();
let mut rng = rand::make_rng::<rngs::SmallRng>();

@@ -326,3 +378,3 @@ for _ in 0..10_000 {

for _ in 0..input_len {
orig_data.push(rng.gen());
orig_data.push(rng.random());
}

@@ -332,7 +384,7 @@

engine.encode_string(&orig_data, &mut encoded_data);
assert_encode_sanity(&encoded_data, engine.config().encode_padding(), input_len);
assert_encode_sanity(&encoded_data, &engine, input_len);
// fill the buffer with random garbage, long enough to have some room before and after
for _ in 0..5000 {
decode_buf.push(rng.gen());
decode_buf.push(rng.random());
}

@@ -376,3 +428,7 @@

DecodeError::InvalidLength(0),
DecodeError::InvalidLastSymbol(0, 0),
DecodeError::InvalidLastSymbol {
offset: 0,
symbol: 0,
symbol_value: 0,
},
DecodeError::InvalidPadding,

@@ -379,0 +435,0 @@ );

@@ -0,2 +1,6 @@

use crate::alphabet::Symbol;
#[cfg(any(feature = "alloc", test))]
use crate::engine::general_purpose::STANDARD;
use crate::engine::{Config, Engine};
#[cfg(any(feature = "alloc", test))]
use alloc::string::String;

@@ -7,10 +11,5 @@ use core::fmt;

#[cfg(any(feature = "alloc", test))]
use crate::engine::general_purpose::STANDARD;
use crate::engine::{Config, Engine};
use crate::PAD_BYTE;
/// Encode arbitrary octets as base64 using the [`STANDARD` engine](STANDARD).
///
/// See [Engine::encode].
/// See [`Engine::encode`].
#[allow(unused)]

@@ -25,3 +24,3 @@ #[deprecated(since = "0.21.0", note = "Use Engine::encode")]

///
/// See [Engine::encode].
/// See [`Engine::encode`].
#[allow(unused)]

@@ -36,3 +35,3 @@ #[deprecated(since = "0.21.0", note = "Use Engine::encode")]

///
/// See [Engine::encode_string].
/// See [`Engine::encode_string`].
#[allow(unused)]

@@ -46,3 +45,3 @@ #[deprecated(since = "0.21.0", note = "Use Engine::encode_string")]

) {
engine.encode_string(input, output_buf)
engine.encode_string(input, output_buf);
}

@@ -52,3 +51,3 @@

///
/// See [Engine::encode_slice].
/// See [`Engine::encode_slice`].
#[allow(unused)]

@@ -66,3 +65,3 @@ #[deprecated(since = "0.21.0", note = "Use Engine::encode_slice")]

///
/// This helper exists to avoid recalculating encoded_size, which is relatively expensive on short
/// This helper exists to avoid recalculating `encoded_size`, which is relatively expensive on short
/// inputs.

@@ -86,3 +85,7 @@ ///

let padding_bytes = if engine.config().encode_padding() {
add_padding(b64_bytes_written, &mut output[b64_bytes_written..])
add_padding(
b64_bytes_written,
engine.padding(),
&mut output[b64_bytes_written..],
)
} else {

@@ -104,2 +107,3 @@ 0

/// input lengths in approximately the top quarter of the range of `usize`.
#[must_use]
pub const fn encoded_len(bytes_len: usize, padding: bool) -> Option<usize> {

@@ -140,3 +144,3 @@ let rem = bytes_len % 3;

/// Returns the number of padding bytes written.
pub(crate) fn add_padding(unpadded_output_len: usize, output: &mut [u8]) -> usize {
pub(crate) fn add_padding(unpadded_output_len: usize, padding: Symbol, output: &mut [u8]) -> usize {
let pad_bytes = (4 - (unpadded_output_len % 4)) % 4;

@@ -147,3 +151,3 @@ // for just a couple bytes, this has better performance than using

for i in 0..pad_bytes {
output[i] = PAD_BYTE;
output[i] = padding.as_u8();
}

@@ -176,2 +180,3 @@

use crate::alphabet::PADDING_SYMBOL;
use crate::{

@@ -182,6 +187,4 @@ alphabet,

};
use rand::{
distributions::{Distribution, Uniform},
Rng, SeedableRng,
};
use rand::distr::{Distribution, Uniform};
use rand::{rngs, RngExt};
use std::str;

@@ -254,6 +257,6 @@

let prefix_len_range = Uniform::new(0, 1000);
let input_len_range = Uniform::new(0, 1000);
let prefix_len_range = Uniform::new(0, 1000).unwrap();
let input_len_range = Uniform::new(0, 1000).unwrap();
let mut rng = rand::rngs::SmallRng::from_entropy();
let mut rng = rand::make_rng::<rngs::SmallRng>();

@@ -270,3 +273,3 @@ for _ in 0..10_000 {

for _ in 0..input_len {
orig_data.push(rng.gen());
orig_data.push(rng.random());
}

@@ -290,12 +293,4 @@

);
assert_encode_sanity(
&encoded_data_no_prefix,
engine.config().encode_padding(),
input_len,
);
assert_encode_sanity(
&encoded_data_with_prefix[prefix_len..],
engine.config().encode_padding(),
input_len,
);
assert_encode_sanity(&encoded_data_no_prefix, &engine, input_len);
assert_encode_sanity(&encoded_data_with_prefix[prefix_len..], &engine, input_len);

@@ -321,5 +316,5 @@ // append plain encode onto prefix

let input_len_range = Uniform::new(0, 1000);
let input_len_range = Uniform::new(0, 1000).unwrap();
let mut rng = rand::rngs::SmallRng::from_entropy();
let mut rng = rand::make_rng::<rngs::SmallRng>();

@@ -335,3 +330,3 @@ for _ in 0..10_000 {

for _ in 0..input_len {
orig_data.push(rng.gen());
orig_data.push(rng.random());
}

@@ -341,3 +336,3 @@

for _ in 0..10 * input_len {
encoded_data.push(rng.gen());
encoded_data.push(rng.random());
}

@@ -358,3 +353,3 @@

str::from_utf8(&encoded_data[0..encoded_size]).unwrap(),
engine.config().encode_padding(),
&engine,
input_len,

@@ -380,5 +375,5 @@ );

let input_len_range = Uniform::new(0, 1000);
let input_len_range = Uniform::new(0, 1000).unwrap();
let mut rng = rand::rngs::SmallRng::from_entropy();
let mut rng = rand::make_rng::<rngs::SmallRng>();

@@ -392,3 +387,3 @@ for _ in 0..10_000 {

for _ in 0..input_len {
input.push(rng.gen());
input.push(rng.random());
}

@@ -402,3 +397,3 @@

for _ in 0..encoded_size {
output.push(rng.gen());
output.push(rng.random());
}

@@ -423,5 +418,5 @@

let input_len_range = Uniform::new(0, 1000);
let input_len_range = Uniform::new(0, 1000).unwrap();
let mut rng = rand::rngs::SmallRng::from_entropy();
let mut rng = rand::make_rng::<rngs::SmallRng>();

@@ -435,3 +430,3 @@ for _ in 0..10_000 {

for _ in 0..input_len {
input.push(rng.gen());
input.push(rng.random());
}

@@ -444,3 +439,3 @@

for _ in 0..encoded_size + 1000 {
output.push(rng.gen());
output.push(rng.random());
}

@@ -464,3 +459,3 @@

let mut rng = rand::rngs::SmallRng::from_entropy();
let mut rng = rand::make_rng::<rngs::SmallRng>();

@@ -473,3 +468,3 @@ // cover our bases for length % 4

for _ in 0..100 {
output.push(rng.gen());
output.push(rng.random());
}

@@ -479,3 +474,3 @@

let bytes_written = add_padding(unpadded_output_len, &mut output);
let bytes_written = add_padding(unpadded_output_len, PADDING_SYMBOL, &mut output);

@@ -499,10 +494,10 @@ // make sure the part beyond bytes_written is the same garbage it was before

let mut bytes: Vec<u8> = Vec::new();
let mut rng = rand::rngs::SmallRng::from_entropy();
let mut rng = rand::make_rng::<rngs::SmallRng>();
for _ in 0..input_len {
bytes.push(rng.gen());
bytes.push(rng.random());
}
let encoded = engine.encode(&bytes);
assert_encode_sanity(&encoded, padded, input_len);
assert_encode_sanity(&encoded, engine, input_len);

@@ -509,0 +504,0 @@ assert_eq!(enc_len, encoded.len());

@@ -0,4 +1,5 @@

use crate::alphabet::Symbol;
use crate::{
engine::{general_purpose::INVALID_VALUE, DecodeMetadata, DecodePaddingMode},
DecodeError, DecodeSliceError, PAD_BYTE,
DecodeError, DecodeSliceError,
};

@@ -11,2 +12,3 @@

/// indicated as already written by `output_index`.
#[allow(clippy::too_many_arguments)]
pub(crate) fn decode_suffix(

@@ -19,2 +21,3 @@ input: &[u8],

decode_allow_trailing_bits: bool,
padding: Symbol,
padding_mode: DecodePaddingMode,

@@ -31,2 +34,3 @@ ) -> Result<DecodeMetadata, DecodeSliceError> {

let mut last_symbol = 0_u8;
let mut last_symbol_value = 0_u8;
let mut morsels = [0_u8; 4];

@@ -36,3 +40,3 @@

// '=' padding
if b == PAD_BYTE {
if b == padding.as_u8() {
// There can be bad padding bytes in a few ways:

@@ -74,5 +78,7 @@ // 1 - Padding with non-padding characters after it

if padding_bytes_count > 0 {
return Err(
DecodeError::InvalidByte(input_index + first_padding_offset, PAD_BYTE).into(),
);
return Err(DecodeError::InvalidByte(
input_index + first_padding_offset,
padding.as_u8(),
)
.into());
}

@@ -85,2 +91,3 @@

let morsel = decode_table[b as usize];
last_symbol_value = morsel;
if morsel == INVALID_VALUE {

@@ -142,6 +149,7 @@ return Err(DecodeError::InvalidByte(input_index + leftover_index, b).into());

// last morsel is at `morsels_in_leftover` - 1
return Err(DecodeError::InvalidLastSymbol(
input_index + morsels_in_leftover - 1,
last_symbol,
)
return Err(DecodeError::InvalidLastSymbol {
offset: input_index + morsels_in_leftover - 1,
symbol: last_symbol,
symbol_value: last_symbol_value,
}
.into());

@@ -148,0 +156,0 @@ }

@@ -0,4 +1,5 @@

use crate::alphabet::Symbol;
use crate::{
engine::{general_purpose::INVALID_VALUE, DecodeEstimate, DecodeMetadata, DecodePaddingMode},
DecodeError, DecodeSliceError, PAD_BYTE,
DecodeError, DecodeSliceError,
};

@@ -18,3 +19,3 @@

rem,
conservative_decoded_len: (encoded_len / 4 + (rem > 0) as usize) * 3,
conservative_decoded_len: (encoded_len / 4 + usize::from(rem > 0)) * 3,
}

@@ -30,3 +31,3 @@ }

/// Helper to avoid duplicating num_chunks calculation, which is costly on short inputs.
/// Helper to avoid duplicating `num_chunks` calculation, which is costly on short inputs.
/// Returns the decode metadata, or an error.

@@ -36,31 +37,96 @@ // We're on the fragile edge of compiler heuristics here. If this is not inlined, slow. If this is

// but this is fragile and the best setting changes with only minor code modifications.
#[allow(clippy::too_many_arguments)]
#[inline]
pub(crate) fn decode_helper(
input: &[u8],
estimate: GeneralPurposeEstimate,
estimate: &GeneralPurposeEstimate,
output: &mut [u8],
decode_table: &[u8; 256],
decode_allow_trailing_bits: bool,
padding: Symbol,
padding_mode: DecodePaddingMode,
simd_prefix: impl FnOnce(&[u8], usize, &mut [u8]) -> (usize, usize),
) -> Result<DecodeMetadata, DecodeSliceError> {
let input_complete_nonterminal_quads_len =
complete_quads_len(input, estimate.rem, output.len(), decode_table)?;
complete_quads_len(input, estimate.rem, output.len(), decode_table, padding)?;
let output_complete_quad_len = input_complete_nonterminal_quads_len / 4 * 3;
// A SIMD backend, when applicable, decodes a leading prefix; the scalar loops decode the rest.
// `simd_prefix` returns `(input_consumed, output_written)` and must consume only whole, valid
// quads: `input_consumed % 4 == 0` and `<= input_complete_nonterminal_quads_len` (the terminal
// quad is left to `decode_suffix`), `output_written == input_consumed / 4 * 3`, and only those
// output bytes are written. It stops on the first invalid/ambiguous quad so the scalar decoder
// reports the precise error offset. `(0, 0)` (pure scalar) is always valid.
let (input_index, output_index) =
simd_prefix(input, input_complete_nonterminal_quads_len, output);
debug_assert!(input_index % 4 == 0, "prefix must consume whole quads");
debug_assert!(
input_index <= input_complete_nonterminal_quads_len,
"prefix must not consume the terminal quad"
);
debug_assert!(
output_index == input_index / 4 * 3,
"prefix output must match consumed input"
);
decode_complete_quads(
input,
input_index,
input_complete_nonterminal_quads_len,
decode_table,
output,
output_index,
)?;
super::decode_suffix::decode_suffix(
input,
input_complete_nonterminal_quads_len,
output,
output_complete_quad_len,
decode_table,
decode_allow_trailing_bits,
padding,
padding_mode,
)
}
/// Decode the complete non-terminal quads in `input[input_index_start..input_index_end]` (both
/// bounds multiples of 4), writing to `output` starting at `output_index_start`, which must equal
/// `input_index_start / 4 * 3`. Error offsets are reported in absolute input coordinates.
#[inline]
fn decode_complete_quads(
input: &[u8],
input_index_start: usize,
input_index_end: usize,
decode_table: &[u8; 256],
output: &mut [u8],
output_index_start: usize,
) -> Result<(), DecodeSliceError> {
debug_assert!(
input_index_start % 4 == 0,
"quad start must be quad-aligned"
);
debug_assert!(input_index_end % 4 == 0, "quad end must be quad-aligned");
debug_assert!(
output_index_start == input_index_start / 4 * 3,
"output start must match consumed input"
);
const UNROLLED_INPUT_CHUNK_SIZE: usize = 32;
const UNROLLED_OUTPUT_CHUNK_SIZE: usize = UNROLLED_INPUT_CHUNK_SIZE / 4 * 3;
let input_complete_quads_after_unrolled_chunks_len =
input_complete_nonterminal_quads_len % UNROLLED_INPUT_CHUNK_SIZE;
let quads_len = input_index_end - input_index_start;
let unrolled_loop_len = quads_len - quads_len % UNROLLED_INPUT_CHUNK_SIZE;
let input_unrolled_loop_end = input_index_start + unrolled_loop_len;
let input_unrolled_loop_len =
input_complete_nonterminal_quads_len - input_complete_quads_after_unrolled_chunks_len;
// chunks of 32 bytes
for (chunk_index, chunk) in input[..input_unrolled_loop_len]
for (chunk_index, chunk) in input[input_index_start..input_unrolled_loop_end]
.chunks_exact(UNROLLED_INPUT_CHUNK_SIZE)
.enumerate()
{
let input_index = chunk_index * UNROLLED_INPUT_CHUNK_SIZE;
let chunk_output = &mut output[chunk_index * UNROLLED_OUTPUT_CHUNK_SIZE
..(chunk_index + 1) * UNROLLED_OUTPUT_CHUNK_SIZE];
let input_index = input_index_start + chunk_index * UNROLLED_INPUT_CHUNK_SIZE;
let output_base = output_index_start + chunk_index * UNROLLED_OUTPUT_CHUNK_SIZE;
let chunk_output = &mut output[output_base..output_base + UNROLLED_OUTPUT_CHUNK_SIZE];

@@ -94,32 +160,19 @@ decode_chunk_8(

// remaining quads, except for the last possibly partial one, as it may have padding
let output_unrolled_loop_len = input_unrolled_loop_len / 4 * 3;
let output_complete_quad_len = input_complete_nonterminal_quads_len / 4 * 3;
let output_after_unroll_start = output_index_start + unrolled_loop_len / 4 * 3;
for (chunk_index, chunk) in input[input_unrolled_loop_end..input_index_end]
.chunks_exact(4)
.enumerate()
{
let output_after_unroll = &mut output[output_unrolled_loop_len..output_complete_quad_len];
let output_base = output_after_unroll_start + chunk_index * 3;
let chunk_output = &mut output[output_base..output_base + 3];
for (chunk_index, chunk) in input
[input_unrolled_loop_len..input_complete_nonterminal_quads_len]
.chunks_exact(4)
.enumerate()
{
let chunk_output = &mut output_after_unroll[chunk_index * 3..chunk_index * 3 + 3];
decode_chunk_4(
chunk,
input_unrolled_loop_len + chunk_index * 4,
decode_table,
chunk_output,
)?;
}
decode_chunk_4(
chunk,
input_unrolled_loop_end + chunk_index * 4,
decode_table,
chunk_output,
)?;
}
super::decode_suffix::decode_suffix(
input,
input_complete_nonterminal_quads_len,
output,
output_complete_quad_len,
decode_table,
decode_allow_trailing_bits,
padding_mode,
)
Ok(())
}

@@ -140,2 +193,3 @@

decode_table: &[u8; 256],
padding: Symbol,
) -> Result<usize, DecodeSliceError> {

@@ -148,3 +202,3 @@ debug_assert!(input.len() % 4 == input_len_rem);

// exclude pad bytes; might be part of padding that extends from earlier in the input
if last_byte != PAD_BYTE && decode_table[usize::from(last_byte)] == INVALID_VALUE {
if last_byte != padding.as_u8() && decode_table[usize::from(last_byte)] == INVALID_VALUE {
return Err(DecodeError::InvalidByte(input.len() - 1, last_byte).into());

@@ -159,3 +213,3 @@ }

// if rem was 0, subtract 4 to avoid padding
.saturating_sub((input_len_rem == 0) as usize * 4);
.saturating_sub(usize::from(input_len_rem == 0) * 4);
debug_assert!(

@@ -261,3 +315,3 @@ input.is_empty() || (1..=4).contains(&(input.len() - input_complete_nonterminal_quads_len))

/// Like [decode_chunk_8] but for 4 bytes of input and 3 bytes of output.
/// Like [`decode_chunk_8`] but for 4 bytes of input and 3 bytes of output.
#[inline(always)]

@@ -264,0 +318,0 @@ fn decode_chunk_4(

@@ -1,2 +0,5 @@

//! Provides the [GeneralPurpose] engine and associated config types.
//! Provides the [`GeneralPurpose`] engine and associated config types.
//!
//! See preconfigured engines like [`STANDARD_NO_PAD`] or [`STANDARD_NO_PAD_INDIFFERENT`].
use crate::alphabet::Symbol;
use crate::{

@@ -19,3 +22,4 @@ alphabet,

///
/// - It uses no vector CPU instructions, so it will work on any system.
/// - It uses no vector CPU instructions, so it will work on any system. For a version that uses
/// SIMD where available, see the SIMD engines behind the `simd-unsafe` feature.
/// - It is reasonably fast (~2-3GiB/s).

@@ -28,5 +32,12 @@ /// - It is not constant-time, though, so it is vulnerable to timing side-channel attacks. For loading cryptographic keys, etc, it is suggested to use the forthcoming constant-time implementation.

decode_table: [u8; 256],
pub(crate) padding: Symbol,
config: GeneralPurposeConfig,
}
/// A purely scalar base64 engine that never uses hardware-specific vector instructions.
///
/// This is an alias for [`GeneralPurpose`], giving an explicit name for callers who want to
/// guarantee a scalar-only implementation.
pub type Scalar = GeneralPurpose;
impl GeneralPurpose {

@@ -37,2 +48,3 @@ /// Create a `GeneralPurpose` engine from an [Alphabet].

/// if the engine will be used repeatedly.
#[must_use]
pub const fn new(alphabet: &Alphabet, config: GeneralPurposeConfig) -> Self {

@@ -42,5 +54,30 @@ Self {

decode_table: decode_table(alphabet),
padding: alphabet.padding,
config,
}
}
/// The 6-bit-index-to-ASCII encode table.
#[cfg(all(
feature = "simd-unsafe",
any(
target_arch = "x86_64",
all(target_arch = "aarch64", target_feature = "neon")
)
))]
pub(crate) fn encode_table(&self) -> &[u8; 64] {
&self.encode_table
}
/// The ASCII-to-6-bit-value decode table.
#[cfg(all(
feature = "simd-unsafe",
any(
target_arch = "x86_64",
all(target_arch = "aarch64", target_feature = "neon")
)
))]
pub(crate) fn decode_table(&self) -> &[u8; 256] {
&self.decode_table
}
}

@@ -53,143 +90,185 @@

fn internal_encode(&self, input: &[u8], output: &mut [u8]) -> usize {
let mut input_index: usize = 0;
encode_helper(&self.encode_table, input, output, |_, _| (0, 0))
}
const BLOCKS_PER_FAST_LOOP: usize = 4;
const LOW_SIX_BITS: u64 = 0x3F;
fn internal_decoded_len_estimate(&self, input_len: usize) -> Self::DecodeEstimate {
GeneralPurposeEstimate::new(input_len)
}
// we read 8 bytes at a time (u64) but only actually consume 6 of those bytes. Thus, we need
// 2 trailing bytes to be available to read..
let last_fast_index = input.len().saturating_sub(BLOCKS_PER_FAST_LOOP * 6 + 2);
let mut output_index = 0;
fn internal_decode(
&self,
input: &[u8],
output: &mut [u8],
estimate: Self::DecodeEstimate,
) -> Result<DecodeMetadata, DecodeSliceError> {
decode::decode_helper(
input,
&estimate,
output,
&self.decode_table,
self.config.decode_allow_trailing_bits,
self.padding,
self.config.decode_padding_mode,
|_, _, _| (0, 0),
)
}
if last_fast_index > 0 {
while input_index <= last_fast_index {
// Major performance wins from letting the optimizer do the bounds check once, mostly
// on the output side
let input_chunk =
&input[input_index..(input_index + (BLOCKS_PER_FAST_LOOP * 6 + 2))];
let output_chunk =
&mut output[output_index..(output_index + BLOCKS_PER_FAST_LOOP * 8)];
fn config(&self) -> &Self::Config {
&self.config
}
// Hand-unrolling for 32 vs 16 or 8 bytes produces yields performance about equivalent
// to unsafe pointer code on a Xeon E5-1650v3. 64 byte unrolling was slightly better for
// large inputs but significantly worse for 50-byte input, unsurprisingly. I suspect
// that it's a not uncommon use case to encode smallish chunks of data (e.g. a 64-byte
// SHA-512 digest), so it would be nice if that fit in the unrolled loop at least once.
// Plus, single-digit percentage performance differences might well be quite different
// on different hardware.
fn padding(&self) -> Symbol {
self.padding
}
}
let input_u64 = read_u64(&input_chunk[0..]);
/// Scalar base64 encode of `input` into `output`, returning the number of bytes written.
///
/// `simd_prefix` gets first crack at the input, returning `(input_consumed, output_written)`. It
/// must consume whole 3-byte groups: `input_consumed % 3 == 0`, `output_written == input_consumed /
/// 3 * 4`, both in bounds, and only those `output_written` bytes are written. `(0, 0)` (pure scalar)
/// is always valid.
#[inline]
pub(crate) fn encode_helper(
encode_table: &[u8; 64],
input: &[u8],
output: &mut [u8],
simd_prefix: impl FnOnce(&[u8], &mut [u8]) -> (usize, usize),
) -> usize {
let (input_index, output_index) = simd_prefix(input, output);
output_chunk[0] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[1] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[2] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[3] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[4] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[5] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[6] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[7] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
debug_assert!(
input_index % 3 == 0,
"prefix must consume whole 3-byte groups"
);
debug_assert!(
output_index == input_index / 3 * 4,
"prefix output must match consumed input"
);
debug_assert!(input_index <= input.len());
debug_assert!(output_index <= output.len());
let input_u64 = read_u64(&input_chunk[6..]);
encode_scalar_tail(encode_table, input, output, input_index, output_index)
}
output_chunk[8] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[9] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[10] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[11] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[12] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[13] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[14] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[15] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
/// Scalar encode of `input[input_index..]` into `output[output_index..]`, resuming from a 3-byte
/// group boundary. Returns the total number of output bytes written.
fn encode_scalar_tail(
encode_table: &[u8; 64],
input: &[u8],
output: &mut [u8],
mut input_index: usize,
mut output_index: usize,
) -> usize {
const BLOCKS_PER_FAST_LOOP: usize = 4;
const LOW_SIX_BITS: u64 = 0x3F;
let input_u64 = read_u64(&input_chunk[12..]);
// we read 8 bytes at a time (u64) but only actually consume 6 of those bytes. Thus, we need
// 2 trailing bytes to be available to read..
let last_fast_index = input.len().saturating_sub(BLOCKS_PER_FAST_LOOP * 6 + 2);
output_chunk[16] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[17] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[18] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[19] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[20] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[21] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[22] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[23] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
if last_fast_index > 0 {
while input_index <= last_fast_index {
// Major performance wins from letting the optimizer do the bounds check once, mostly
// on the output side
let input_chunk = &input[input_index..(input_index + (BLOCKS_PER_FAST_LOOP * 6 + 2))];
let output_chunk = &mut output[output_index..(output_index + BLOCKS_PER_FAST_LOOP * 8)];
let input_u64 = read_u64(&input_chunk[18..]);
// Hand-unrolling for 32 vs 16 or 8 bytes produces yields performance about equivalent
// to unsafe pointer code on a Xeon E5-1650v3. 64 byte unrolling was slightly better for
// large inputs but significantly worse for 50-byte input, unsurprisingly. I suspect
// that it's a not uncommon use case to encode smallish chunks of data (e.g. a 64-byte
// SHA-512 digest), so it would be nice if that fit in the unrolled loop at least once.
// Plus, single-digit percentage performance differences might well be quite different
// on different hardware.
output_chunk[24] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[25] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[26] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[27] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[28] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[29] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[30] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[31] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
let input_u64 = read_u64(&input_chunk[0..]);
output_index += BLOCKS_PER_FAST_LOOP * 8;
input_index += BLOCKS_PER_FAST_LOOP * 6;
}
}
output_chunk[0] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[1] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[2] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[3] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[4] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[5] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[6] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[7] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
// Encode what's left after the fast loop.
let input_u64 = read_u64(&input_chunk[6..]);
const LOW_SIX_BITS_U8: u8 = 0x3F;
output_chunk[8] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[9] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[10] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[11] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[12] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[13] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[14] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[15] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
let rem = input.len() % 3;
let start_of_rem = input.len() - rem;
let input_u64 = read_u64(&input_chunk[12..]);
// start at the first index not handled by fast loop, which may be 0.
output_chunk[16] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[17] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[18] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[19] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[20] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[21] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[22] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[23] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
while input_index < start_of_rem {
let input_chunk = &input[input_index..(input_index + 3)];
let output_chunk = &mut output[output_index..(output_index + 4)];
let input_u64 = read_u64(&input_chunk[18..]);
output_chunk[0] = self.encode_table[(input_chunk[0] >> 2) as usize];
output_chunk[1] = self.encode_table
[((input_chunk[0] << 4 | input_chunk[1] >> 4) & LOW_SIX_BITS_U8) as usize];
output_chunk[2] = self.encode_table
[((input_chunk[1] << 2 | input_chunk[2] >> 6) & LOW_SIX_BITS_U8) as usize];
output_chunk[3] = self.encode_table[(input_chunk[2] & LOW_SIX_BITS_U8) as usize];
output_chunk[24] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[25] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[26] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[27] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[28] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[29] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[30] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[31] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
input_index += 3;
output_index += 4;
output_index += BLOCKS_PER_FAST_LOOP * 8;
input_index += BLOCKS_PER_FAST_LOOP * 6;
}
}
if rem == 2 {
output[output_index] = self.encode_table[(input[start_of_rem] >> 2) as usize];
output[output_index + 1] =
self.encode_table[((input[start_of_rem] << 4 | input[start_of_rem + 1] >> 4)
& LOW_SIX_BITS_U8) as usize];
output[output_index + 2] =
self.encode_table[((input[start_of_rem + 1] << 2) & LOW_SIX_BITS_U8) as usize];
output_index += 3;
} else if rem == 1 {
output[output_index] = self.encode_table[(input[start_of_rem] >> 2) as usize];
output[output_index + 1] =
self.encode_table[((input[start_of_rem] << 4) & LOW_SIX_BITS_U8) as usize];
output_index += 2;
}
// Encode what's left after the fast loop.
output_index
}
const LOW_SIX_BITS_U8: u8 = 0x3F;
fn internal_decoded_len_estimate(&self, input_len: usize) -> Self::DecodeEstimate {
GeneralPurposeEstimate::new(input_len)
let rem = input.len() % 3;
let start_of_rem = input.len() - rem;
// start at the first index not handled by fast loop, which may be 0.
while input_index < start_of_rem {
let input_chunk = &input[input_index..(input_index + 3)];
let output_chunk = &mut output[output_index..(output_index + 4)];
output_chunk[0] = encode_table[(input_chunk[0] >> 2) as usize];
output_chunk[1] =
encode_table[((input_chunk[0] << 4 | input_chunk[1] >> 4) & LOW_SIX_BITS_U8) as usize];
output_chunk[2] =
encode_table[((input_chunk[1] << 2 | input_chunk[2] >> 6) & LOW_SIX_BITS_U8) as usize];
output_chunk[3] = encode_table[(input_chunk[2] & LOW_SIX_BITS_U8) as usize];
input_index += 3;
output_index += 4;
}
fn internal_decode(
&self,
input: &[u8],
output: &mut [u8],
estimate: Self::DecodeEstimate,
) -> Result<DecodeMetadata, DecodeSliceError> {
decode::decode_helper(
input,
estimate,
output,
&self.decode_table,
self.config.decode_allow_trailing_bits,
self.config.decode_padding_mode,
)
if rem == 2 {
output[output_index] = encode_table[(input[start_of_rem] >> 2) as usize];
output[output_index + 1] = encode_table[((input[start_of_rem] << 4
| input[start_of_rem + 1] >> 4)
& LOW_SIX_BITS_U8) as usize];
output[output_index + 2] =
encode_table[((input[start_of_rem + 1] << 2) & LOW_SIX_BITS_U8) as usize];
output_index += 3;
} else if rem == 1 {
output[output_index] = encode_table[(input[start_of_rem] >> 2) as usize];
output[output_index + 1] =
encode_table[((input[start_of_rem] << 4) & LOW_SIX_BITS_U8) as usize];
output_index += 2;
}
fn config(&self) -> &Self::Config {
&self.config
}
output_index
}

@@ -214,3 +293,3 @@

/// Returns a table mapping base64 bytes as the lookup index to either:
/// - [INVALID_VALUE] for bytes that aren't members of the alphabet
/// - [`INVALID_VALUE`] for bytes that aren't members of the alphabet
/// - a byte whose lower 6 bits are the value that was encoded into the index byte

@@ -222,3 +301,3 @@ pub(crate) const fn decode_table(alphabet: &Alphabet) -> [u8; 256] {

// the parts that are valid.
let mut index = 0;
let mut index = 0_usize;
while index < 64 {

@@ -248,3 +327,3 @@ // The index in the alphabet is the 6-bit value we care about.

///
/// The constants [PAD] and [NO_PAD] cover most use cases.
/// The constants [PAD] and [`NO_PAD`] cover most use cases.
///

@@ -265,2 +344,3 @@ /// To specify the characters used, see [Alphabet].

/// a few bytes unless you specifically need it for compatibility with some legacy system.
#[must_use]
pub const fn new() -> Self {

@@ -285,2 +365,3 @@ Self {

/// padding to be present.
#[must_use]
pub const fn with_encode_padding(self, padding: bool) -> Self {

@@ -300,2 +381,3 @@ Self {

/// be silently ignored, else `DecodeError::InvalidLastSymbol` will be emitted.
#[must_use]
pub const fn with_decode_allow_trailing_bits(self, allow: bool) -> Self {

@@ -321,2 +403,3 @@ Self {

/// next multiple of four, there's `DecodePaddingMode::RequireNoPadding`.
#[must_use]
pub const fn with_decode_padding_mode(self, mode: DecodePaddingMode) -> Self {

@@ -331,3 +414,3 @@ Self {

impl Default for GeneralPurposeConfig {
/// Delegates to [GeneralPurposeConfig::new].
/// Delegates to [`GeneralPurposeConfig::new`].
fn default() -> Self {

@@ -344,23 +427,96 @@ Self::new()

/// A [GeneralPurpose] engine using the [alphabet::STANDARD] base64 alphabet and [PAD] config.
#[cfg(all(
feature = "simd-unsafe",
any(
target_arch = "x86_64",
all(target_arch = "aarch64", target_feature = "neon")
)
))]
impl GeneralPurposeConfig {
/// Whether trailing bits are allowed when decoding.
pub(crate) fn decode_allow_trailing_bits(&self) -> bool {
self.decode_allow_trailing_bits
}
/// The decode padding mode.
pub(crate) fn decode_padding_mode(&self) -> DecodePaddingMode {
self.decode_padding_mode
}
}
/// A [`GeneralPurpose`] engine using the [`alphabet::STANDARD`] base64 alphabet and [`PAD`] config.
///
/// Does not allow trailing bits when decoding.
pub const STANDARD: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, PAD);
/// A [GeneralPurpose] engine using the [alphabet::STANDARD] base64 alphabet and [NO_PAD] config.
/// A [`GeneralPurpose`] engine using the [`alphabet::STANDARD`] base64 alphabet and
/// [`PAD_INDIFFERENT`] config.
///
/// Does not allow trailing bits when decoding.
pub const STANDARD_PAD_INDIFFERENT: GeneralPurpose =
GeneralPurpose::new(&alphabet::STANDARD, PAD_INDIFFERENT);
/// A [`GeneralPurpose`] engine using the [`alphabet::STANDARD`] base64 alphabet and [`NO_PAD`] config.
///
/// Does not allow trailing bits when decoding.
pub const STANDARD_NO_PAD: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, NO_PAD);
/// A [GeneralPurpose] engine using the [alphabet::URL_SAFE] base64 alphabet and [PAD] config.
/// A [`GeneralPurpose`] engine using the [`alphabet::STANDARD`] base64 alphabet and
/// [`NO_PAD_INDIFFERENT`] config.
///
/// Does not allow trailing bits when decoding.
pub const STANDARD_NO_PAD_INDIFFERENT: GeneralPurpose =
GeneralPurpose::new(&alphabet::STANDARD, NO_PAD_INDIFFERENT);
/// A [`GeneralPurpose`] engine using the [`alphabet::URL_SAFE`] base64 alphabet and [`PAD`] config.
///
/// Does not allow trailing bits when decoding.
pub const URL_SAFE: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, PAD);
/// A [GeneralPurpose] engine using the [alphabet::URL_SAFE] base64 alphabet and [NO_PAD] config.
/// A [`GeneralPurpose`] engine using the [`alphabet::URL_SAFE`] base64 alphabet and
/// [`PAD_INDIFFERENT`] config.
///
/// Does not allow trailing bits when decoding.
pub const URL_SAFE_PAD_INDIFFERENT: GeneralPurpose =
GeneralPurpose::new(&alphabet::URL_SAFE, PAD_INDIFFERENT);
/// A [`GeneralPurpose`] engine using the [`alphabet::URL_SAFE`] base64 alphabet and [`NO_PAD`] config.
///
/// Does not allow trailing bits when decoding.
pub const URL_SAFE_NO_PAD: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, NO_PAD);
/// A [`GeneralPurpose`] engine using the [`alphabet::URL_SAFE`] base64 alphabet and
/// [`NO_PAD_INDIFFERENT`] config.
///
/// Does not allow trailing bits when decoding.
pub const URL_SAFE_NO_PAD_INDIFFERENT: GeneralPurpose =
GeneralPurpose::new(&alphabet::URL_SAFE, NO_PAD_INDIFFERENT);
/// Include padding bytes when encoding, and require that they be present when decoding.
///
/// This is the standard per the base64 RFC, but consider using [NO_PAD] instead as padding serves
/// little purpose in practice.
/// Does not allow trailing bits when decoding.
///
/// This is the standard per the base64 RFC, but consider using [`NO_PAD`] or [`NO_PAD_INDIFFERENT`]
/// instead as padding serves little purpose in practice.
pub const PAD: GeneralPurposeConfig = GeneralPurposeConfig::new();
/// Don't add padding when encoding, and require no padding when decoding.
/// Include padding bytes when encoding, but allow input with or without padding when decoding.
///
/// Does not allow trailing bits when decoding.
pub const PAD_INDIFFERENT: GeneralPurposeConfig = GeneralPurposeConfig::new()
.with_encode_padding(true)
.with_decode_padding_mode(DecodePaddingMode::Indifferent);
/// Don't add padding when encoding, and require that there is no padding when decoding.
///
/// Does not allow trailing bits when decoding.
pub const NO_PAD: GeneralPurposeConfig = GeneralPurposeConfig::new()
.with_encode_padding(false)
.with_decode_padding_mode(DecodePaddingMode::RequireNone);
/// Don't add padding when encoding, and allow input with or without padding when decoding.
///
/// Does not allow trailing bits when decoding.
pub const NO_PAD_INDIFFERENT: GeneralPurposeConfig = GeneralPurposeConfig::new()
.with_encode_padding(false)
.with_decode_padding_mode(DecodePaddingMode::Indifferent);
//! Provides the [Engine] abstraction and out of the box implementations.
use crate::alphabet::Symbol;
#[cfg(any(feature = "alloc", test))]

@@ -16,2 +17,11 @@ use crate::chunked_encoder;

#[cfg(all(
feature = "simd-unsafe",
any(
target_arch = "x86_64",
all(target_arch = "aarch64", target_feature = "neon")
)
))]
pub mod simd;
#[cfg(test)]

@@ -23,11 +33,34 @@ mod naive;

pub use general_purpose::{GeneralPurpose, GeneralPurposeConfig};
pub use general_purpose::{GeneralPurpose, GeneralPurposeConfig, Scalar};
/// The runtime-detected SIMD engine. Requires the `simd-unsafe` feature.
#[cfg(all(
feature = "simd-unsafe",
feature = "std",
any(
target_arch = "x86_64",
all(target_arch = "aarch64", target_feature = "neon")
)
))]
pub use simd::Simd;
/// The AVX2 engine. Requires the `simd-unsafe` feature on an `x86_64` target.
#[cfg(all(feature = "simd-unsafe", target_arch = "x86_64"))]
pub use simd::Avx2;
/// The NEON engine. Requires the `simd-unsafe` feature on an `aarch64` target.
#[cfg(all(
feature = "simd-unsafe",
target_arch = "aarch64",
target_feature = "neon"
))]
pub use simd::Neon;
/// An `Engine` provides low-level encoding and decoding operations that all other higher-level parts of the API use. Users of the library will generally not need to implement this.
///
/// Different implementations offer different characteristics. The library currently ships with
/// [GeneralPurpose] that offers good speed and works on any CPU, with more choices
/// [`GeneralPurpose`] that offers good speed and works on any CPU, with more choices
/// coming later, like a constant-time one when side channel resistance is called for, and vendor-specific vectorized ones for more speed.
///
/// See [general_purpose::STANDARD_NO_PAD] if you just want standard base64. Otherwise, when possible, it's
/// See [`general_purpose::STANDARD_NO_PAD`] if you just want standard base64. Otherwise, when possible, it's
/// recommended to store the engine in a `const` so that references to it won't pose any lifetime

@@ -86,3 +119,3 @@ /// issues, and to avoid repeating the cost of engine setup.

///
/// Non-canonical trailing bits in the final tokens or non-canonical padding must be reported as
/// Non-canonical trailing bits in the final symbols or non-canonical padding must be reported as
/// errors unless the engine is configured otherwise.

@@ -170,3 +203,3 @@ #[doc(hidden)]

inner(self, input.as_ref(), output_buf)
inner(self, input.as_ref(), output_buf);
}

@@ -352,5 +385,5 @@

///
/// See [crate::decoded_len_estimate] for calculating buffer sizes.
/// See [`crate::decoded_len_estimate`] for calculating buffer sizes.
///
/// See [Engine::decode_slice_unchecked] for a version that panics instead of returning an error
/// See [`Engine::decode_slice_unchecked`] for a version that panics instead of returning an error
/// if the output buffer is too small.

@@ -389,5 +422,5 @@ #[inline]

///
/// See [crate::decoded_len_estimate] for calculating buffer sizes.
/// See [`crate::decoded_len_estimate`] for calculating buffer sizes.
///
/// See [Engine::decode_slice] for a version that returns an error instead of panicking if the output
/// See [`Engine::decode_slice`] for a version that returns an error instead of panicking if the output
/// buffer is too small.

@@ -425,2 +458,7 @@ ///

}
/// Returns the symbol used for encode padding.
///
/// Typically this is `'='`, but weird alphabets may use other values.
fn padding(&self) -> Symbol;
}

@@ -432,3 +470,3 @@

///
/// Padding is added outside the engine's encode() since the engine may be used
/// Padding is added outside the engine's `encode()` since the engine may be used
/// to encode only a chunk of the overall output, so it can't always know when

@@ -452,3 +490,3 @@ /// the output is "done" and would therefore need padding (if configured).

/// The estimate must be no larger than the next largest complete triple of decoded bytes.
/// That is, the final quad of tokens to decode may be assumed to be complete with no padding.
/// That is, the final quad of symbols to decode may be assumed to be complete with no padding.
fn decoded_len_estimate(&self) -> usize;

@@ -460,3 +498,3 @@ }

/// Each [Engine] must support at least the behavior indicated by
/// [DecodePaddingMode::RequireCanonical], and may support other modes.
/// [`DecodePaddingMode::RequireCanonical`], and may support other modes.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]

@@ -463,0 +501,0 @@ pub enum DecodePaddingMode {

@@ -0,1 +1,2 @@

use crate::alphabet::Symbol;
use crate::{

@@ -15,2 +16,3 @@ alphabet::Alphabet,

decode_table: [u8; 256],
pub(crate) padding: Symbol,
config: NaiveConfig,

@@ -27,2 +29,3 @@ }

decode_table: decode_table(alphabet),
padding: alphabet.padding,
config,

@@ -122,2 +125,3 @@ }

&self.decode_table,
self.padding,
)?;

@@ -153,2 +157,3 @@

self.config.decode_allow_trailing_bits,
self.padding,
self.config.decode_padding_mode,

@@ -161,2 +166,6 @@ )

}
fn padding(&self) -> Symbol {
self.padding
}
}

@@ -163,0 +172,0 @@

@@ -49,3 +49,3 @@ //! Correct, fast, and configurable [base64][] decoding and encoding. Base64

//!
//! The standard alphabet uses `+` and `/` as its two non-alphanumeric tokens,
//! The standard alphabet uses `+` and `/` as its two non-alphanumeric symbols,
//! which cannot be safely used in URL’s without encoding them as `%2B` and

@@ -118,2 +118,4 @@ //! `%2F`.

//!
//! Padding serves no practical purpose, so where possible, encode without padding.
//!
//! ### Further customization

@@ -193,2 +195,29 @@ //!

//!
//! This also allows for constant-space validity checking of encoded data, using a
//! statically allocated buffer:
//!
#![cfg_attr(feature = "std", doc = "```")]
#![cfg_attr(not(feature = "std"), doc = "```ignore")]
//! # use std::io::{self, Read};
//! use base64::{engine::general_purpose::STANDARD, read::DecoderReader};
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let mut invalid_input = "dt==";
//! let mut decoder = DecoderReader::new(io::Cursor::new(&mut invalid_input), &STANDARD);
//!
//! let mut buf = [0u8; 128];
//!
//! let is_valid = loop {
//! match decoder.read(&mut buf) {
//! Ok(0) => break true, // Read to end w/o error
//! Ok(_) => continue,
//! Err(_) => break false,
//! }
//! };
//!
//! assert!(!is_valid);
//! # Ok(())
//! # }
//! ```
//!
//! #### Encoding

@@ -222,2 +251,22 @@ //!

//!
//! # Crate features
//!
//! - `std` (default): enables `std::io` integration, [`std::error::Error`] impls, and heap
//! allocation. Implies `alloc`.
//! - `alloc`: enables the allocating APIs (e.g. [`Engine::encode`], [`Engine::decode`]) in a
//! `no_std` build.
//! - `simd-unsafe`: enables the SIMD-accelerated engines. It is on by default and is the only
//! feature that introduces `unsafe` code; with it disabled the crate is
//! `#![forbid(unsafe_code)]`.
//!
//! ## SIMD acceleration
//!
//! With the `simd-unsafe` feature, the [`engine`] module provides SIMD engines for the standard and
//! URL-safe alphabets that are several times faster than [`GeneralPurpose`][engine::GeneralPurpose]:
//!
//! - `Simd` picks the best available instruction set (AVX2 on `x86_64`, NEON on `aarch64`) at
//! runtime and falls back to the scalar engine. It needs `std` for the CPU-feature detection.
//! - `Avx2` and `Neon` target one instruction set without runtime detection, so they can be used in
//! `no_std` builds when the target is known to support the instructions.
//!
//! # Panics

@@ -227,3 +276,2 @@ //!

#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))]
#![deny(

@@ -238,6 +286,7 @@ missing_docs,

)]
#![forbid(unsafe_code)]
// Allow globally until https://github.com/rust-lang/rust-clippy/issues/8768 is resolved.
// The desired state is to allow it only for the rstest_reuse import.
#![allow(clippy::single_component_path_imports)]
// The `simd-unsafe` feature (on by default) is the only source of `unsafe`; without it the crate
// is `#![forbid(unsafe_code)]`. When it is enabled, `unsafe` is confined to the SIMD engine module,
// which opts back in with a localized `allow`.
#![cfg_attr(not(feature = "simd-unsafe"), forbid(unsafe_code))]
#![cfg_attr(feature = "simd-unsafe", deny(unsafe_code))]
#![cfg_attr(not(any(feature = "std", test)), no_std)]

@@ -248,6 +297,2 @@

// has to be included at top level because of the way rstest_reuse defines its macros
#[cfg(test)]
use rstest_reuse;
mod chunked_encoder;

@@ -283,3 +328,1 @@ pub mod display;

mod tests;
const PAD_BYTE: u8 = b'=';

@@ -6,2 +6,4 @@ //! Preconfigured engines for common use cases.

//!
//! All of these engine presets enforce no trailing bits when decoding.
//!
//! # Examples

@@ -8,0 +10,0 @@ //!

@@ -7,3 +7,3 @@ use std::{

use rand::{Rng as _, RngCore as _};
use rand::{Rng as _, RngExt};

@@ -15,3 +15,3 @@ use super::decoder::{DecoderReader, BUF_SIZE};

tests::{random_alphabet, random_config, random_engine},
DecodeError, PAD_BYTE,
DecodeError,
};

@@ -93,3 +93,3 @@

fn handles_short_read_from_delegate() {
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
let mut bytes = Vec::new();

@@ -104,3 +104,3 @@ let mut b64 = String::new();

let size = rng.gen_range(0..(10 * BUF_SIZE));
let size = rng.random_range(0..(10 * BUF_SIZE));
bytes.extend(iter::repeat(0).take(size));

@@ -130,3 +130,3 @@ bytes.truncate(size);

fn read_in_short_increments() {
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
let mut bytes = Vec::new();

@@ -141,3 +141,3 @@ let mut b64 = String::new();

let size = rng.gen_range(0..(10 * BUF_SIZE));
let size = rng.random_range(0..(10 * BUF_SIZE));
bytes.extend(iter::repeat(0).take(size));

@@ -163,3 +163,3 @@ // leave room to play around with larger buffers

fn read_in_short_increments_with_short_delegate_reads() {
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
let mut bytes = Vec::new();

@@ -174,3 +174,3 @@ let mut b64 = String::new();

let size = rng.gen_range(0..(10 * BUF_SIZE));
let size = rng.random_range(0..(10 * BUF_SIZE));
bytes.extend(iter::repeat(0).take(size));

@@ -191,3 +191,3 @@ // leave room to play around with larger buffers

delegate: &mut decoder,
rng: &mut rand::thread_rng(),
rng: &mut rand::rng(),
};

@@ -206,3 +206,3 @@

fn reports_invalid_last_symbol_correctly() {
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
let mut bytes = Vec::new();

@@ -219,3 +219,3 @@ let mut b64 = String::new();

let size = rng.gen_range(1..(10 * BUF_SIZE));
let size = rng.random_range(1..(10 * BUF_SIZE));
bytes.extend(iter::repeat(0).take(size));

@@ -229,3 +229,3 @@ decoded.extend(iter::repeat(0).take(size));

// changing padding will cause invalid padding errors when we twiddle the last byte
let engine = GeneralPurpose::new(alphabet, config.with_encode_padding(false));
let engine = GeneralPurpose::new(&alphabet, config.with_encode_padding(false));
engine.encode_string(&bytes[..], &mut b64);

@@ -260,3 +260,3 @@ b64_bytes.extend(b64.bytes());

fn reports_invalid_byte_correctly() {
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
let mut bytes = Vec::new();

@@ -273,3 +273,3 @@ let mut b64 = String::new();

let size = rng.gen_range(1..(10 * BUF_SIZE));
let size = rng.random_range(1..(10 * BUF_SIZE));
bytes.extend(iter::repeat(0).take(size));

@@ -283,3 +283,3 @@ rng.fill_bytes(&mut bytes[..size]);

// replace one byte, somewhere, with '*', which is invalid
let bad_byte_pos = rng.gen_range(0..b64.len());
let bad_byte_pos = rng.random_range(0..b64.len());
let mut b64_bytes = b64.bytes().collect::<Vec<u8>>();

@@ -317,3 +317,3 @@ b64_bytes[bad_byte_pos] = b'*';

fn internal_padding_error_with_short_read_concatenated_texts_invalid_byte_error() {
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
let mut bytes = Vec::new();

@@ -335,3 +335,3 @@ let mut b64 = String::new();

// at least 2 bytes so there can be a split point between bytes
let size = rng.gen_range(2..(10 * BUF_SIZE));
let size = rng.random_range(2..(10 * BUF_SIZE));
bytes.resize(size, 0);

@@ -346,3 +346,3 @@ rng.fill_bytes(&mut bytes[..size]);

// find a split point that will produce padding on the first part
let s = rng.gen_range(1..size);
let s = rng.random_range(1..size);
if s % 3 != 0 {

@@ -361,3 +361,3 @@ // short enough to need padding

// short read to make it plausible for padding to happen on a read boundary
let read_len = rng.gen_range(1..10);
let read_len = rng.random_range(1..10);
let mut wrapped_reader = ShortRead {

@@ -397,3 +397,3 @@ max_read_len: read_len,

},
PAD_BYTE
engine.padding.as_u8()
),

@@ -407,3 +407,3 @@ read_decode_err

fn internal_padding_anywhere_error() {
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
let mut bytes = Vec::new();

@@ -433,6 +433,6 @@ let mut b64 = String::new();

// put padding somewhere other than the last quad
b64_bytes[rng.gen_range(0..bytes.len() - 4)] = PAD_BYTE;
b64_bytes[rng.random_range(0..bytes.len() - 4)] = engine.padding.as_u8();
// short read to make it plausible for padding to happen on a read boundary
let read_len = rng.gen_range(1..10);
let read_len = rng.random_range(1..10);
let mut wrapped_reader = ShortRead {

@@ -473,3 +473,3 @@ max_read_len: read_len,

}
let decode_len = rng.gen_range(1..cmp::max(2, expected_bytes.len() * 2));
let decode_len = rng.random_range(1..cmp::max(2, expected_bytes.len() * 2));

@@ -494,3 +494,3 @@ let read = short_reader

// avoid 0 since it means EOF for non-empty buffers
let effective_len = cmp::min(self.rng.gen_range(1..20), buf.len());
let effective_len = cmp::min(self.rng.random_range(1..20), buf.len());

@@ -497,0 +497,0 @@ self.delegate.read(&mut buf[..effective_len])

@@ -1,2 +0,2 @@

use crate::{engine::Engine, DecodeError, DecodeSliceError, PAD_BYTE};
use crate::{engine::Engine, DecodeError, DecodeSliceError};
use std::{cmp, fmt, io};

@@ -162,4 +162,9 @@

// padding
(PAD_BYTE, Some(first_pad_offset)) => {
DecodeError::InvalidByte(first_pad_offset, PAD_BYTE)
(byte, Some(first_pad_offset))
if byte == self.engine.padding().as_u8() =>
{
DecodeError::InvalidByte(
first_pad_offset,
self.engine.padding().as_u8(),
)
}

@@ -174,5 +179,11 @@ _ => {

}
DecodeError::InvalidLastSymbol(offset, byte) => {
DecodeError::InvalidLastSymbol(self.input_consumed_len + offset, byte)
}
DecodeError::InvalidLastSymbol {
offset,
symbol,
symbol_value,
} => DecodeError::InvalidLastSymbol {
offset: self.input_consumed_len + offset,
symbol,
symbol_value,
},
DecodeError::InvalidPadding => DecodeError::InvalidPadding,

@@ -193,3 +204,3 @@ }

io::ErrorKind::InvalidData,
DecodeError::InvalidByte(offset, PAD_BYTE),
DecodeError::InvalidByte(offset, self.engine.padding().as_u8()),
));

@@ -228,3 +239,3 @@ }

///
/// Where possible, this function buffers base64 to minimize the number of read() calls to the
/// Where possible, this function buffers base64 to minimize the number of `read()` calls to the
/// delegate reader.

@@ -304,4 +315,4 @@ ///

// if we are at eof, could have less than BASE64_CHUNK_SIZE, in which case we have
// to assume that these last few tokens are, in fact, valid (i.e. must be 2-4 b64
// tokens, not 1, since 1 token can't decode to 1 byte).
// to assume that these last few symbols are, in fact, valid (i.e. must be 2-4 b64
// symbols, not 1, since 1 symbols can't decode to 1 byte).
let to_decode = cmp::min(self.b64_len, BASE64_CHUNK_SIZE);

@@ -308,0 +319,0 @@

use std::str;
use rand::{
distributions,
distributions::{Distribution as _, Uniform},
seq::SliceRandom,
Rng, SeedableRng,
distr,
distr::{Distribution as _, Uniform},
rngs, Rng, RngExt,
};
use crate::alphabet::{is_valid_b64_symbol, Symbol};
use crate::{

@@ -22,3 +22,3 @@ alphabet,

// exercise the slower encode/decode routines that operate on shorter buffers more vigorously
roundtrip_random_config(Uniform::new(0, 50), 10_000);
roundtrip_random_config(Uniform::new(0, 50).unwrap(), 10_000);
}

@@ -28,9 +28,23 @@

fn roundtrip_random_config_long() {
roundtrip_random_config(Uniform::new(0, 1000), 10_000);
roundtrip_random_config(Uniform::new(0, 1000).unwrap(), 10_000);
}
pub fn assert_encode_sanity(encoded: &str, padded: bool, input_len: usize) {
pub fn assert_encode_sanity(encoded: &str, engine: &impl Engine, input_len: usize) {
let expect_padding = engine.config().encode_padding();
let padding_symbol = engine.padding();
assert_encode_sanity_core(encoded, expect_padding, padding_symbol, input_len)
}
/// [`assert_encode_sanity`] when you want separate padding config & padding symbol.
pub fn assert_encode_sanity_core(
encoded: &str,
expect_padding: bool,
padding_symbol: Symbol,
input_len: usize,
) {
let input_rem = input_len % 3;
let expected_padding_len = if input_rem > 0 {
if padded {
if expect_padding {
3 - input_rem

@@ -44,7 +58,10 @@ } else {

let expected_encoded_len = encoded_len(input_len, padded).unwrap();
let expected_encoded_len = encoded_len(input_len, expect_padding).unwrap();
assert_eq!(expected_encoded_len, encoded.len());
let padding_len = encoded.chars().filter(|&c| c == '=').count();
let padding_len = encoded
.bytes()
.filter(|&b| b == padding_symbol.as_u8())
.count();

@@ -59,3 +76,3 @@ assert_eq!(expected_padding_len, padding_len);

let mut encoded_buf = String::new();
let mut rng = rand::rngs::SmallRng::from_entropy();
let mut rng = rand::make_rng::<rngs::SmallRng>();

@@ -71,3 +88,3 @@ for _ in 0..iterations {

for _ in 0..input_len {
input_buf.push(rng.gen());
input_buf.push(rng.random());
}

@@ -77,3 +94,3 @@

assert_encode_sanity(&encoded_buf, engine.config().encode_padding(), input_len);
assert_encode_sanity(&encoded_buf, &engine, input_len);

@@ -85,6 +102,6 @@ assert_eq!(input_buf, engine.decode(&encoded_buf).unwrap());

pub fn random_config<R: Rng>(rng: &mut R) -> GeneralPurposeConfig {
let mode = rng.gen();
let mode = rng.random();
GeneralPurposeConfig::new()
.with_encode_padding(match mode {
DecodePaddingMode::Indifferent => rng.gen(),
DecodePaddingMode::Indifferent => rng.random(),
DecodePaddingMode::RequireCanonical => true,

@@ -94,8 +111,8 @@ DecodePaddingMode::RequireNone => false,

.with_decode_padding_mode(mode)
.with_decode_allow_trailing_bits(rng.gen())
.with_decode_allow_trailing_bits(rng.random())
}
impl distributions::Distribution<DecodePaddingMode> for distributions::Standard {
impl distr::Distribution<DecodePaddingMode> for distr::StandardUniform {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> DecodePaddingMode {
match rng.gen_range(0..=2) {
match rng.random_range(0..=2) {
0 => DecodePaddingMode::Indifferent,

@@ -108,4 +125,17 @@ 1 => DecodePaddingMode::RequireCanonical,

pub fn random_alphabet<R: Rng>(rng: &mut R) -> &'static alphabet::Alphabet {
ALPHABETS.choose(rng).unwrap()
pub fn random_alphabet<R: Rng>(rng: &mut R) -> alphabet::Alphabet {
// 65 symbols for alphabet + padding
let mut symbols = Vec::with_capacity(65);
while symbols.len() < 65 {
let t = rng.random();
if is_valid_b64_symbol(t) && !symbols.contains(&t) {
symbols.push(t);
}
}
alphabet::Alphabet::new_with_padding(
str::from_utf8(&symbols[..64]).unwrap(),
Symbol::new(symbols[64]).unwrap(),
)
.unwrap()
}

@@ -116,12 +146,3 @@

let config = random_config(rng);
GeneralPurpose::new(alphabet, config)
GeneralPurpose::new(&alphabet, config)
}
const ALPHABETS: &[alphabet::Alphabet] = &[
alphabet::URL_SAFE,
alphabet::STANDARD,
alphabet::CRYPT,
alphabet::BCRYPT,
alphabet::IMAP_MUTF7,
alphabet::BIN_HEX,
];

@@ -6,3 +6,3 @@ use super::encoder::EncoderWriter;

/// A `Write` implementation that base64-encodes data using the provided config and accumulates the
/// resulting base64 utf8 `&str` in a [StrConsumer] implementation (typically `String`), which is
/// resulting base64 utf8 `&str` in a [`StrConsumer`] implementation (typically `String`), which is
/// then exposed via `into_inner()`.

@@ -57,3 +57,3 @@ ///

impl<'e, E: Engine, S: StrConsumer> EncoderStringWriter<'e, E, S> {
/// Create a EncoderStringWriter that will append to the provided `StrConsumer`.
/// Create a `EncoderStringWriter` that will append to the provided `StrConsumer`.
pub fn from_consumer(str_consumer: S, engine: &'e E) -> Self {

@@ -78,3 +78,3 @@ EncoderStringWriter {

impl<'e, E: Engine> EncoderStringWriter<'e, E, String> {
/// Create a EncoderStringWriter that will encode into a new `String` with the provided config.
/// Create a `EncoderStringWriter` that will encode into a new `String` with the provided config.
pub fn new(engine: &'e E) -> Self {

@@ -101,3 +101,3 @@ EncoderStringWriter::from_consumer(String::new(), engine)

/// As for io::Write, `StrConsumer` is implemented automatically for `&mut S`.
/// As for `io::Write`, `StrConsumer` is implemented automatically for `&mut S`.
impl<S: StrConsumer + ?Sized> StrConsumer for &mut S {

@@ -145,3 +145,3 @@ fn consume(&mut self, buf: &str) {

};
use rand::Rng;
use rand::RngExt;
use std::cmp;

@@ -152,3 +152,3 @@ use std::io::Write;

fn every_possible_split_of_input() {
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
let mut orig_data = Vec::<u8>::new();

@@ -181,3 +181,3 @@ let mut normal_encoded = String::new();

fn incremental_writes() {
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
let mut orig_data = Vec::<u8>::new();

@@ -202,3 +202,3 @@ let mut normal_encoded = String::new();

while offset < size {
let nibble_size = cmp::min(rng.gen_range(0..=64), size - offset);
let nibble_size = cmp::min(rng.random_range(0..=64), size - offset);
let len = stream_encoder

@@ -205,0 +205,0 @@ .write(&orig_data[offset..offset + nibble_size])

use std::io::{Cursor, Write};
use std::{cmp, io, str};
use rand::Rng;
use rand::{Rng, RngExt};

@@ -268,3 +268,3 @@ use crate::{

fn every_possible_split_of_input() {
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
let mut orig_data = Vec::<u8>::new();

@@ -282,3 +282,3 @@ let mut stream_encoded = Vec::<u8>::new();

for _ in 0..size {
orig_data.push(rng.gen());
orig_data.push(rng.random());
}

@@ -313,3 +313,3 @@

fn retrying_writes_that_error_with_interrupted_works() {
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
let mut orig_data = Vec::<u8>::new();

@@ -324,5 +324,5 @@ let mut stream_encoded = Vec::<u8>::new();

let orig_len: usize = rng.gen_range(100..20_000);
let orig_len: usize = rng.random_range(100..20_000);
for _ in 0..orig_len {
orig_data.push(rng.gen());
orig_data.push(rng.random());
}

@@ -336,3 +336,3 @@

{
let mut interrupt_rng = rand::thread_rng();
let mut interrupt_rng = rand::rng();
let mut interrupting_writer = InterruptingWriter {

@@ -349,3 +349,3 @@ w: &mut stream_encoded,

// when errors occur
let input_len: usize = cmp::min(rng.gen_range(0..10), orig_len - bytes_consumed);
let input_len: usize = cmp::min(rng.random_range(0..10), orig_len - bytes_consumed);

@@ -381,3 +381,3 @@ retry_interrupted_write_all(

fn writes_that_only_write_part_of_input_and_sometimes_interrupt_produce_correct_encoded_data() {
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
let mut orig_data = Vec::<u8>::new();

@@ -392,5 +392,5 @@ let mut stream_encoded = Vec::<u8>::new();

let orig_len: usize = rng.gen_range(100..20_000);
let orig_len: usize = rng.random_range(100..20_000);
for _ in 0..orig_len {
orig_data.push(rng.gen());
orig_data.push(rng.random());
}

@@ -404,3 +404,3 @@

{
let mut partial_rng = rand::thread_rng();
let mut partial_rng = rand::rng();
let mut partial_writer = PartialInterruptingWriter {

@@ -417,3 +417,4 @@ w: &mut stream_encoded,

// use at most medium-length inputs to exercise retry logic more aggressively
let input_len: usize = cmp::min(rng.gen_range(0..100), orig_len - bytes_consumed);
let input_len: usize =
cmp::min(rng.random_range(0..100), orig_len - bytes_consumed);

@@ -464,3 +465,3 @@ let res =

fn do_encode_random_config_matches_normal_encode(max_input_len: usize) {
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
let mut orig_data = Vec::<u8>::new();

@@ -475,5 +476,5 @@ let mut stream_encoded = Vec::<u8>::new();

let orig_len: usize = rng.gen_range(100..20_000);
let orig_len: usize = rng.random_range(100..20_000);
for _ in 0..orig_len {
orig_data.push(rng.gen());
orig_data.push(rng.random());
}

@@ -490,4 +491,6 @@

while bytes_consumed < orig_len {
let input_len: usize =
cmp::min(rng.gen_range(0..max_input_len), orig_len - bytes_consumed);
let input_len: usize = cmp::min(
rng.random_range(0..max_input_len),
orig_len - bytes_consumed,
);

@@ -522,3 +525,3 @@ // write a little bit of the data

fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if self.rng.gen_range(0.0..1.0) <= self.fraction {
if self.rng.random_range(0.0..1.0) <= self.fraction {
return Err(io::Error::new(io::ErrorKind::Interrupted, "interrupted"));

@@ -531,3 +534,3 @@ }

fn flush(&mut self) -> io::Result<()> {
if self.rng.gen_range(0.0..1.0) <= self.fraction {
if self.rng.random_range(0.0..1.0) <= self.fraction {
return Err(io::Error::new(io::ErrorKind::Interrupted, "interrupted"));

@@ -552,7 +555,7 @@ }

fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if self.rng.gen_range(0.0..1.0) > self.no_interrupt_fraction {
if self.rng.random_range(0.0..1.0) > self.no_interrupt_fraction {
return Err(io::Error::new(io::ErrorKind::Interrupted, "interrupted"));
}
if self.rng.gen_range(0.0..1.0) <= self.full_input_fraction || buf.is_empty() {
if self.rng.random_range(0.0..1.0) <= self.full_input_fraction || buf.is_empty() {
// pass through the buf untouched

@@ -563,3 +566,3 @@ self.w.write(buf)

self.w
.write(&buf[0..(self.rng.gen_range(0..(buf.len() - 1)))])
.write(&buf[0..(self.rng.random_range(0..(buf.len() - 1)))])
}

@@ -566,0 +569,0 @@ }

@@ -66,3 +66,3 @@ use crate::engine::Engine;

/// Where encoded data is written to. It's an Option as it's None immediately before Drop is
/// called so that finish() can return the underlying writer. None implies that finish() has
/// called so that `finish()` can return the underlying writer. None implies that `finish()` has
/// been called successfully.

@@ -130,5 +130,6 @@ delegate: Option<W>,

// finish() is retryable in the face of I/O errors, so we can't consume here.
if self.delegate.is_none() {
panic!("Encoder has already had finish() called");
};
assert!(
self.delegate.is_some(),
"Encoder has already had finish() called"
);

@@ -173,3 +174,3 @@ self.write_final_leftovers()?;

/// Write as much of the encoded output to the delegate writer as it will accept, and store the
/// leftovers to be attempted at the next write() call. Updates `self.output_occupied_len`.
/// leftovers to be attempted at the next `write()` call. Updates `self.output_occupied_len`.
///

@@ -207,3 +208,3 @@ /// # Errors

///
/// This is basically write_all for the remaining buffered data but without the undesirable
/// This is basically `write_all` for the remaining buffered data but without the undesirable
/// abort-on-`Ok(0)` behavior.

@@ -224,3 +225,3 @@ ///

// success no-ops because remaining length is already updated
Ok(_) => {}
Ok(()) => {}
};

@@ -271,5 +272,6 @@ }

fn write(&mut self, input: &[u8]) -> Result<usize> {
if self.delegate.is_none() {
panic!("Cannot write more after calling finish()");
}
assert!(
self.delegate.is_some(),
"Cannot write more after calling finish()"
);

@@ -292,3 +294,3 @@ if input.is_empty() {

// did not read any input
.map(|_| 0);
.map(|()| 0);
}

@@ -384,3 +386,3 @@

// input
.map(|_| extra_input_read_len + input_chunks_to_encode_len)
.map(|()| extra_input_read_len + input_chunks_to_encode_len)
.map_err(|e| {

@@ -387,0 +389,0 @@ // in case we filled and encoded `extra`, reset extra_len

@@ -1,2 +0,2 @@

use rand::{Rng, SeedableRng};
use rand::{rngs, RngExt};

@@ -19,3 +19,3 @@ use base64::engine::{general_purpose::STANDARD, Engine};

let num_rounds = calculate_number_of_rounds(byte_len, approx_values_per_byte, max_rounds);
let mut r = rand::rngs::SmallRng::from_entropy();
let mut r = rand::make_rng::<rngs::SmallRng>();
let mut decode_buf = Vec::new();

@@ -28,3 +28,3 @@

while byte_buf.len() < byte_len {
byte_buf.push(r.gen::<u8>());
byte_buf.push(r.random::<u8>());
}

@@ -31,0 +31,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display