| //! 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() | ||
| } | ||
| } |
| { | ||
| "git": { | ||
| "sha1": "e14400697453bcc85997119b874bc03d9601d0af" | ||
| "sha1": "9e9220a4166f628de7c8803289e120ae1e944f78" | ||
| }, | ||
| "path_in_vcs": "" | ||
| } |
+108
-7
@@ -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 |
+0
-1
| 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" |
+37
-23
@@ -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
@@ -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 @@ } |
+27
-3
| # [base64](https://crates.io/crates/base64) | ||
| [](https://crates.io/crates/base64) [](https://docs.rs/base64) [](https://circleci.com/gh/marshallpierce/rust-base64/tree/master) [](https://codecov.io/gh/marshallpierce/rust-base64) [](https://github.com/rust-secure-code/safety-dance/) | ||
| [](https://crates.io/crates/base64) [](https://docs.rs/base64) [](https://circleci.com/gh/marshallpierce/rust-base64/tree/master) [](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. | ||
+11
-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 @@ |
+156
-37
| //! 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 @@ |
+83
-27
@@ -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 @@ ); |
+46
-51
@@ -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); |
+50
-12
| //! 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 @@ |
+55
-12
@@ -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'='; |
+2
-0
@@ -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]) |
+21
-10
@@ -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 @@ |
+51
-30
| 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 @@ } |
+14
-12
@@ -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 |
+3
-3
@@ -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