base64-simd-0.8.0/.cargo_vcs_info.json0000644000000001600000000000100130540ustar { "git": { "sha1": "d74c030d9dc4f3cae02146d1f497ff62726ef09a" }, "path_in_vcs": "crates/base64-simd" }base64-simd-0.8.0/Cargo.toml0000644000000027320000000000100110610ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.63" name = "base64-simd" version = "0.8.0" description = "SIMD-accelerated base64 encoding and decoding" readme = "README.md" keywords = [ "base64", "simd", ] categories = [ "no-std", "parser-implementations", "encoding", ] license = "MIT" repository = "https://github.com/Nugine/simd" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [dependencies.outref] version = "0.5.0" [dependencies.vsimd] version = "0.8.0" [dev-dependencies.base64] version = "0.20.0" [dev-dependencies.const-str] version = "0.5.3" [dev-dependencies.rand] version = "0.8.5" [features] alloc = ["vsimd/alloc"] default = [ "std", "detect", ] detect = ["vsimd/detect"] std = [ "alloc", "vsimd/std", ] unstable = ["vsimd/unstable"] [target."cfg(target_arch=\"wasm32\")".dev-dependencies.getrandom] version = "0.2.8" features = ["js"] [target."cfg(target_arch=\"wasm32\")".dev-dependencies.wasm-bindgen-test] version = "0.3.33" base64-simd-0.8.0/Cargo.toml.orig000064400000000000000000000015211046102023000145350ustar 00000000000000[package] name = "base64-simd" version = "0.8.0" edition = "2021" description = "SIMD-accelerated base64 encoding and decoding" license = "MIT" repository = "https://github.com/Nugine/simd" keywords = ["base64", "simd"] categories = ["no-std", "parser-implementations", "encoding"] readme = "README.md" rust-version = "1.63" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] default = ["std", "detect"] alloc = ["vsimd/alloc"] std = ["alloc", "vsimd/std"] detect = ["vsimd/detect"] unstable = ["vsimd/unstable"] [dependencies] outref = "0.5.0" vsimd = { path = "../vsimd", version = "0.8.0" } [dev-dependencies] base64 = "0.20.0" rand = "0.8.5" const-str = "0.5.3" [target.'cfg(target_arch="wasm32")'.dev-dependencies] getrandom = { version = "0.2.8", features = ["js"] } wasm-bindgen-test = "0.3.33" base64-simd-0.8.0/README.md000064400000000000000000000007171046102023000131330ustar 00000000000000# base64-simd [![Crates.io](https://img.shields.io/crates/v/base64-simd.svg)](https://crates.io/crates/base64-simd) [![Docs](https://docs.rs/base64-simd/badge.svg)](https://docs.rs/base64-simd/) [![MIT licensed][mit-badge]][mit-url] [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg [mit-url]: ../../LICENSE SIMD-accelerated base64 encoding and decoding. Documentation: Repository: base64-simd-0.8.0/src/alsw.rs000064400000000000000000000043361046102023000137600ustar 00000000000000use vsimd::alsw::AlswLut; use vsimd::vector::{V128, V256}; struct StandardAlsw; impl StandardAlsw { #[inline] const fn decode(c: u8) -> u8 { match c { b'A'..=b'Z' => c - b'A', b'a'..=b'z' => c - b'a' + 26, b'0'..=b'9' => c - b'0' + 52, b'+' => 62, b'/' => 63, _ => 0xff, } } #[inline] const fn check_hash(i: u8) -> u8 { match i { 0 => 5, 1..=9 => 2, 0xA => 4, 0xB => 6, 0xC..=0xE => 8, 0xF => 6, _ => unreachable!(), } } #[inline] const fn decode_hash(i: u8) -> u8 { match i { 0xB => 0x07, 0xF => 0x08, _ => 0x01, } } } vsimd::impl_alsw!(StandardAlsw); struct UrlSafeAlsw; impl UrlSafeAlsw { #[inline] const fn decode(c: u8) -> u8 { match c { b'A'..=b'Z' => c - b'A', b'a'..=b'z' => c - b'a' + 26, b'0'..=b'9' => c - b'0' + 52, b'-' => 62, b'_' => 63, _ => 0xff, } } #[inline] const fn check_hash(i: u8) -> u8 { match i { 0 => 7, 1..=9 => 2, 0xA => 4, 0xB | 0xC => 6, 0xD => 8, 0xE => 6, 0xF => 6, _ => unreachable!(), } } #[inline] const fn decode_hash(i: u8) -> u8 { match i { 0xD => 0x01, 0xF => 0x05, _ => 0x01, } } } vsimd::impl_alsw!(UrlSafeAlsw); pub const STANDARD_ALSW_CHECK_X2: AlswLut = StandardAlsw::check_lut().x2(); pub const STANDARD_ALSW_DECODE_X2: AlswLut = StandardAlsw::decode_lut().x2(); pub const URL_SAFE_ALSW_CHECK_X2: AlswLut = UrlSafeAlsw::check_lut().x2(); pub const URL_SAFE_ALSW_DECODE_X2: AlswLut = UrlSafeAlsw::decode_lut().x2(); #[cfg(test)] mod algorithm { use super::*; #[test] #[ignore] fn standard_alsw() { StandardAlsw::test_check(); StandardAlsw::test_decode(); } #[test] #[ignore] fn url_safe_alsw() { UrlSafeAlsw::test_check(); UrlSafeAlsw::test_decode(); } } base64-simd-0.8.0/src/ascii.rs000064400000000000000000000121641046102023000141000ustar 00000000000000use vsimd::isa::AVX2; use vsimd::tools::slice_parts; use vsimd::{matches_isa, Scalable, POD, SIMD256}; use core::ops::Not; #[inline(always)] #[must_use] fn lookup_ascii_whitespace(c: u8) -> u8 { const TABLE: &[u8; 256] = &{ let mut ans = [0; 256]; let mut i: u8 = 0; loop { ans[i as usize] = if i.is_ascii_whitespace() { 0xff } else { 0 }; if i == 255 { break; } i += 1; } ans }; unsafe { *TABLE.get_unchecked(c as usize) } } #[inline(always)] fn has_ascii_whitespace, V: POD>(s: S, x: V) -> bool { // ASCII whitespaces // TAB 0x09 00001001 // LF 0x0a 00001010 // FF 0x0c 00001100 // CR 0x0d 00001101 // SPACE 0x20 00010000 // // m1 = {{byte in 0x09..=0x0d}}x32 let m1 = s.i8xn_lt(s.u8xn_sub(x, s.u8xn_splat(0x89)), s.i8xn_splat(-128 + 5)); // m2 = {{byte == 0x0b}} let m2 = s.u8xn_eq(x, s.u8xn_splat(0x0b)); // m3 = {{byte is SPACE}} let m3 = s.u8xn_eq(x, s.u8xn_splat(0x20)); // any((m1 & !m2) | m3) s.mask8xn_any(s.or(s.andnot(m1, m2), m3)) } #[inline(always)] unsafe fn find_non_ascii_whitespace_short(mut src: *const u8, len: usize) -> usize { let base = src; let end = base.add(len); while src < end { if lookup_ascii_whitespace(src.read()) != 0 { break; } src = src.add(1); } src.offset_from(base) as usize } #[inline(always)] pub unsafe fn find_non_ascii_whitespace_fallback(src: *const u8, len: usize) -> usize { find_non_ascii_whitespace_short(src, len) } #[inline(always)] pub unsafe fn find_non_ascii_whitespace_simd(s: S, mut src: *const u8, len: usize) -> usize { let base = src; if matches_isa!(S, AVX2) { let end = src.add(len / 32 * 32); while src < end { let x = s.v256_load_unaligned(src); if has_ascii_whitespace(s, x) { break; } src = src.add(32); } if (len % 32) >= 16 { let x = s.v128_load_unaligned(src); if has_ascii_whitespace(s, x).not() { src = src.add(16); } } } else { let end = src.add(len / 16 * 16); while src < end { let x = s.v128_load_unaligned(src); if has_ascii_whitespace(s, x) { break; } src = src.add(16); } } let checked_len = src.offset_from(base) as usize; let pos = find_non_ascii_whitespace_short(src, len - checked_len); checked_len + pos } #[inline(always)] #[must_use] pub fn find_non_ascii_whitespace(data: &[u8]) -> usize { let (src, len) = slice_parts(data); unsafe { crate::multiversion::find_non_ascii_whitespace::auto(src, len) } } #[inline(always)] #[must_use] pub unsafe fn remove_ascii_whitespace_fallback(mut src: *const u8, len: usize, mut dst: *mut u8) -> usize { let dst_base = dst; let end = src.add(len); while src < end { let x = src.read(); if lookup_ascii_whitespace(x) == 0 { dst.write(x); dst = dst.add(1); } src = src.add(1); } dst.offset_from(dst_base) as usize } #[inline(always)] #[must_use] pub fn remove_ascii_whitespace_inplace(data: &mut [u8]) -> &mut [u8] { let pos = find_non_ascii_whitespace(data); debug_assert!(pos <= data.len()); if pos == data.len() { return data; } unsafe { let len = data.len() - pos; let dst = data.as_mut_ptr().add(pos); let src = dst; let rem = remove_ascii_whitespace_fallback(src, len, dst); debug_assert!(rem <= len); data.get_unchecked_mut(..(pos + rem)) } } #[cfg(test)] mod tests { use super::*; #[cfg_attr(not(target_arch = "wasm32"), test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn test_remove_ascii_whitespace() { let cases = [ "\0\0\0\0", "abcd", "ab\tcd", "ab\ncd", "ab\x0Ccd", "ab\rcd", "ab cd", "ab\t\n\x0C\r cd", "ab\t\n\x0C\r =\t\n\x0C\r =\t\n\x0C\r ", ]; let check = |case: &str, repeat: usize| { let mut buf = case.repeat(repeat).into_bytes(); let expected = { let mut v = buf.clone(); v.retain(|c| !c.is_ascii_whitespace()); v }; let ans = remove_ascii_whitespace_inplace(&mut buf); assert_eq!(ans, &*expected, "case = {case:?}"); }; for case in cases { check(case, 1); if cfg!(not(miri)) { check(case, 10); } } } } #[cfg(test)] mod algorithm { #[test] #[ignore] fn is_ascii_whitespace() { for x in 0..=255u8 { let m1 = (x.wrapping_sub(0x89) as i8) < (-128 + 5); let m2 = x == 0x0b; let m3 = x == 0x20; let ans = (m1 && !m2) || m3; assert_eq!(ans, x.is_ascii_whitespace()); } } } base64-simd-0.8.0/src/check.rs000064400000000000000000000035101046102023000140600ustar 00000000000000use crate::alsw::{STANDARD_ALSW_CHECK_X2, URL_SAFE_ALSW_CHECK_X2}; use crate::decode::{decode_ascii4, decode_ascii8, decode_extra}; use crate::decode::{STANDARD_DECODE_TABLE, URL_SAFE_DECODE_TABLE}; use crate::{Config, Error, Kind}; use vsimd::alsw::AlswLut; use vsimd::vector::V256; use vsimd::SIMD256; use core::ptr::null_mut; #[inline] pub(crate) unsafe fn check_fallback(mut src: *const u8, mut n: usize, config: Config) -> Result<(), Error> { let kind = config.kind; let forgiving = config.extra.forgiving(); let table = match kind { Kind::Standard => STANDARD_DECODE_TABLE.as_ptr(), Kind::UrlSafe => URL_SAFE_DECODE_TABLE.as_ptr(), }; unsafe { // n*3/4 >= 6+2 while n >= 11 { decode_ascii8::(src, null_mut(), table)?; src = src.add(8); n -= 8; } while n >= 4 { decode_ascii4::(src, null_mut(), table)?; src = src.add(4); n -= 4; } decode_extra::(n, src, null_mut(), table, forgiving) } } #[inline(always)] pub(crate) unsafe fn check_simd( s: S, mut src: *const u8, mut n: usize, config: Config, ) -> Result<(), Error> { let kind = config.kind; let check_lut = match kind { Kind::Standard => STANDARD_ALSW_CHECK_X2, Kind::UrlSafe => URL_SAFE_ALSW_CHECK_X2, }; unsafe { // n*3/4 >= 24+4 while n >= 38 { let x = s.v256_load_unaligned(src); let is_valid = check_ascii32(s, x, check_lut); ensure!(is_valid); src = src.add(32); n -= 32; } check_fallback(src, n, config) } } #[inline(always)] fn check_ascii32(s: S, x: V256, check: AlswLut) -> bool { vsimd::alsw::check_ascii_xn(s, x, check) } base64-simd-0.8.0/src/decode.rs000064400000000000000000000172171046102023000142370ustar 00000000000000use crate::alsw::{STANDARD_ALSW_CHECK_X2, URL_SAFE_ALSW_CHECK_X2}; use crate::alsw::{STANDARD_ALSW_DECODE_X2, URL_SAFE_ALSW_DECODE_X2}; use crate::{Config, Error, Extra, Kind}; use crate::{STANDARD_CHARSET, URL_SAFE_CHARSET}; use vsimd::alsw::AlswLut; use vsimd::isa::{NEON, SSSE3, WASM128}; use vsimd::mask::u8x32_highbit_any; use vsimd::matches_isa; use vsimd::tools::{read, write}; use vsimd::vector::V256; use vsimd::SIMD256; use core::ops::Not; const fn decode_table(charset: &'static [u8; 64]) -> [u8; 256] { let mut table = [0xff; 256]; let mut i = 0; while i < charset.len() { table[charset[i] as usize] = i as u8; i += 1; } table } pub const STANDARD_DECODE_TABLE: &[u8; 256] = &decode_table(STANDARD_CHARSET); pub const URL_SAFE_DECODE_TABLE: &[u8; 256] = &decode_table(URL_SAFE_CHARSET); #[inline(always)] pub(crate) fn decoded_length(src: &[u8], config: Config) -> Result<(usize, usize), Error> { if src.is_empty() { return Ok((0, 0)); } let n = unsafe { let len = src.len(); let count_pad = || { let last1 = *src.get_unchecked(len - 1); let last2 = *src.get_unchecked(len - 2); if last1 == b'=' { if last2 == b'=' { 2 } else { 1 } } else { 0 } }; match config.extra { Extra::Pad => { ensure!(len % 4 == 0); len - count_pad() } Extra::NoPad => len, Extra::Forgiving => { if len % 4 == 0 { len - count_pad() } else { len } } } }; let m = match n % 4 { 0 => n / 4 * 3, 1 => return Err(Error::new()), 2 => n / 4 * 3 + 1, 3 => n / 4 * 3 + 2, _ => unsafe { core::hint::unreachable_unchecked() }, }; Ok((n, m)) } #[inline(always)] pub unsafe fn decode_ascii8(src: *const u8, dst: *mut u8, table: *const u8) -> Result<(), Error> { let mut y: u64 = 0; let mut flag = 0; let mut i = 0; while i < 8 { let x = read(src, i); let bits = read(table, x as usize); flag |= bits; if WRITE { y |= (bits as u64) << (58 - i * 6); } i += 1; } if WRITE { dst.cast::().write_unaligned(y.to_be()); } ensure!(flag != 0xff); Ok(()) } #[inline(always)] pub unsafe fn decode_ascii4(src: *const u8, dst: *mut u8, table: *const u8) -> Result<(), Error> { let mut y: u32 = 0; let mut flag = 0; let mut i = 0; while i < 4 { let x = read(src, i); let bits = read(table, x as usize); flag |= bits; if WRITE { y |= (bits as u32) << (18 - i * 6); } i += 1; } if WRITE { let y = y.to_be_bytes(); write(dst, 0, y[1]); write(dst, 1, y[2]); write(dst, 2, y[3]); } ensure!(flag != 0xff); Ok(()) } #[inline(always)] pub unsafe fn decode_extra( extra: usize, src: *const u8, dst: *mut u8, table: *const u8, forgiving: bool, ) -> Result<(), Error> { match extra { 0 => {} 1 => core::hint::unreachable_unchecked(), 2 => { let [x1, x2] = src.cast::<[u8; 2]>().read(); let y1 = read(table, x1 as usize); let y2 = read(table, x2 as usize); ensure!((y1 | y2) != 0xff && (forgiving || (y2 & 0x0f) == 0)); if WRITE { write(dst, 0, (y1 << 2) | (y2 >> 4)); } } 3 => { let [x1, x2, x3] = src.cast::<[u8; 3]>().read(); let y1 = read(table, x1 as usize); let y2 = read(table, x2 as usize); let y3 = read(table, x3 as usize); ensure!((y1 | y2 | y3) != 0xff && (forgiving || (y3 & 0x03) == 0)); if WRITE { write(dst, 0, (y1 << 2) | (y2 >> 4)); write(dst, 1, (y2 << 4) | (y3 >> 2)); } } _ => core::hint::unreachable_unchecked(), } Ok(()) } #[inline] pub(crate) unsafe fn decode_fallback( mut src: *const u8, mut dst: *mut u8, mut n: usize, config: Config, ) -> Result<(), Error> { let kind = config.kind; let forgiving = config.extra.forgiving(); let table = match kind { Kind::Standard => STANDARD_DECODE_TABLE.as_ptr(), Kind::UrlSafe => URL_SAFE_DECODE_TABLE.as_ptr(), }; // n*3/4 >= 6+2 while n >= 11 { decode_ascii8::(src, dst, table)?; src = src.add(8); dst = dst.add(6); n -= 8; } let end = src.add(n / 4 * 4); while src < end { decode_ascii4::(src, dst, table)?; src = src.add(4); dst = dst.add(3); } n %= 4; decode_extra::(n, src, dst, table, forgiving) } #[inline(always)] pub(crate) unsafe fn decode_simd( s: S, mut src: *const u8, mut dst: *mut u8, mut n: usize, config: Config, ) -> Result<(), Error> { let kind = config.kind; let (check_lut, decode_lut) = match kind { Kind::Standard => (STANDARD_ALSW_CHECK_X2, STANDARD_ALSW_DECODE_X2), Kind::UrlSafe => (URL_SAFE_ALSW_CHECK_X2, URL_SAFE_ALSW_DECODE_X2), }; // n*3/4 >= 24+4 while n >= 38 { let x = s.v256_load_unaligned(src); let y = try_!(decode_ascii32(s, x, check_lut, decode_lut)); let (y1, y2) = y.to_v128x2(); s.v128_store_unaligned(dst, y1); s.v128_store_unaligned(dst.add(12), y2); src = src.add(32); dst = dst.add(24); n -= 32; } decode_fallback(src, dst, n, config) } #[inline(always)] fn merge_bits_x2(s: S, x: V256) -> V256 { // x : {00aaaaaa|00bbbbbb|00cccccc|00dddddd} x8 let y = if matches_isa!(S, SSSE3) { let m1 = s.u16x16_splat(u16::from_le_bytes([0x40, 0x01])); let x1 = s.i16x16_maddubs(x, m1); // x1: {aabbbbbb|0000aaaa|ccdddddd|0000cccc} x8 let m2 = s.u32x8_splat(u32::from_le_bytes([0x00, 0x10, 0x01, 0x00])); s.i16x16_madd(x1, m2) // {ccdddddd|bbbbcccc|aaaaaabb|00000000} x8 } else if matches_isa!(S, NEON | WASM128) { let m1 = s.u32x8_splat(u32::from_le_bytes([0x3f, 0x00, 0x3f, 0x00])); let x1 = s.v256_and(x, m1); // x1: {00aaaaaa|00000000|00cccccc|00000000} x8 let m2 = s.u32x8_splat(u32::from_le_bytes([0x00, 0x3f, 0x00, 0x3f])); let x2 = s.v256_and(x, m2); // x2: {00000000|00bbbbbb|00000000|00dddddd} x8 let x3 = s.v256_or(s.u32x8_shl::<18>(x1), s.u32x8_shr::<10>(x1)); // x3: {cc000000|0000cccc|aaaaaa00|00000000} x8 let x4 = s.v256_or(s.u32x8_shl::<4>(x2), s.u32x8_shr::<24>(x2)); // x4: {00dddddd|bbbb0000|000000bb|dddd0000} let mask = s.u32x8_splat(u32::from_le_bytes([0xff, 0xff, 0xff, 0x00])); s.v256_and(s.v256_or(x3, x4), mask) // {ccdddddd|bbbbcccc|aaaaaabb|00000000} x8 } else { unreachable!() }; const SHUFFLE: V256 = V256::double_bytes([ 0x02, 0x01, 0x00, 0x06, 0x05, 0x04, 0x0a, 0x09, // 0x08, 0x0e, 0x0d, 0x0c, 0x80, 0x80, 0x80, 0x80, // ]); s.u8x16x2_swizzle(y, SHUFFLE) // {AAAB|BBCC|CDDD|0000|EEEF|FFGG|GHHH|0000} } #[inline(always)] fn decode_ascii32(s: S, x: V256, check: AlswLut, decode: AlswLut) -> Result { let (c1, c2) = vsimd::alsw::decode_ascii_xn(s, x, check, decode); let y = merge_bits_x2(s, c2); ensure!(u8x32_highbit_any(s, c1).not()); Ok(y) } base64-simd-0.8.0/src/encode.rs000064400000000000000000000241731046102023000142500ustar 00000000000000use crate::{Config, Kind}; use crate::{STANDARD_CHARSET, URL_SAFE_CHARSET}; use vsimd::isa::{NEON, SSE2, WASM128}; use vsimd::tools::{read, write}; use vsimd::vector::{V128, V256}; use vsimd::{matches_isa, POD}; use vsimd::{Scalable, SIMD128, SIMD256}; #[inline(always)] pub(crate) const fn encoded_length_unchecked(len: usize, config: Config) -> usize { let extra = len % 3; if extra == 0 { len / 3 * 4 } else if config.extra.padding() { len / 3 * 4 + 4 } else { len / 3 * 4 + extra + 1 } } #[inline(always)] unsafe fn encode_bits24(src: *const u8, dst: *mut u8, charset: *const u8) { let x = u32::from_be_bytes([0, read(src, 0), read(src, 1), read(src, 2)]); let mut i = 0; while i < 4 { let bits = (x >> (18 - i * 6)) & 0x3f; let y = read(charset, bits as usize); write(dst, i, y); i += 1; } } #[inline(always)] unsafe fn encode_bits48(src: *const u8, dst: *mut u8, charset: *const u8) { let x = u64::from_be_bytes(src.cast::<[u8; 8]>().read()); let mut i = 0; while i < 8 { let bits = (x >> (58 - i * 6)) & 0x3f; let y = read(charset, bits as usize); write(dst, i, y); i += 1; } } #[inline(always)] unsafe fn encode_extra(extra: usize, src: *const u8, dst: *mut u8, charset: *const u8, padding: bool) { match extra { 0 => {} 1 => { let x = read(src, 0); let y1 = read(charset, (x >> 2) as usize); let y2 = read(charset, ((x << 6) >> 2) as usize); write(dst, 0, y1); write(dst, 1, y2); if padding { write(dst, 2, b'='); write(dst, 3, b'='); } } 2 => { let x1 = read(src, 0); let x2 = read(src, 1); let y1 = read(charset, (x1 >> 2) as usize); let y2 = read(charset, (((x1 << 6) >> 2) | (x2 >> 4)) as usize); let y3 = read(charset, ((x2 << 4) >> 2) as usize); write(dst, 0, y1); write(dst, 1, y2); write(dst, 2, y3); if padding { write(dst, 3, b'='); } } _ => core::hint::unreachable_unchecked(), } } #[inline] pub(crate) unsafe fn encode_fallback(mut src: *const u8, mut len: usize, mut dst: *mut u8, config: Config) { let kind = config.kind; let padding = config.extra.padding(); let charset = match kind { Kind::Standard => STANDARD_CHARSET.as_ptr(), Kind::UrlSafe => URL_SAFE_CHARSET.as_ptr(), }; const L: usize = 4; while len >= L * 6 + 2 { let mut i = 0; while i < L { encode_bits48(src, dst, charset); src = src.add(6); dst = dst.add(8); i += 1; } len -= L * 6; } while len >= 6 + 2 { encode_bits48(src, dst, charset); src = src.add(6); dst = dst.add(8); len -= 6; } let end = src.add(len / 3 * 3); while src < end { encode_bits24(src, dst, charset); src = src.add(3); dst = dst.add(4); } len %= 3; encode_extra(len, src, dst, charset, padding); } #[inline(always)] pub(crate) unsafe fn encode_simd( s: S, mut src: *const u8, mut len: usize, mut dst: *mut u8, config: Config, ) { let kind = config.kind; if len >= (6 + 24 + 4) { let (charset, shift_lut) = match kind { Kind::Standard => (STANDARD_CHARSET.as_ptr(), STANDARD_ENCODING_SHIFT_X2), Kind::UrlSafe => (URL_SAFE_CHARSET.as_ptr(), URL_SAFE_ENCODING_SHIFT_X2), }; for _ in 0..2 { encode_bits24(src, dst, charset); src = src.add(3); dst = dst.add(4); len -= 3; } while len >= (24 + 4) { let x = s.v256_load_unaligned(src.sub(4)); let y = encode_bytes24(s, x, shift_lut); s.v256_store_unaligned(dst, y); src = src.add(24); dst = dst.add(32); len -= 24; } } if len >= 12 + 4 { let shift_lut = match kind { Kind::Standard => STANDARD_ENCODING_SHIFT, Kind::UrlSafe => URL_SAFE_ENCODING_SHIFT, }; let x = s.v128_load_unaligned(src); let y = encode_bytes12(s, x, shift_lut); s.v128_store_unaligned(dst, y); src = src.add(12); dst = dst.add(16); len -= 12; } encode_fallback(src, len, dst, config); } const SPLIT_SHUFFLE: V256 = V256::from_bytes([ 0x05, 0x04, 0x06, 0x05, 0x08, 0x07, 0x09, 0x08, // 0x0b, 0x0a, 0x0c, 0x0b, 0x0e, 0x0d, 0x0f, 0x0e, // 0x01, 0x00, 0x02, 0x01, 0x04, 0x03, 0x05, 0x04, // 0x07, 0x06, 0x08, 0x07, 0x0a, 0x09, 0x0b, 0x0a, // ]); #[inline(always)] fn split_bits_x2(s: S, x: V256) -> V256 { // x: {????|AAAB|BBCC|CDDD|EEEF|FFGG|GHHH|????} let x0 = s.u8x16x2_swizzle(x, SPLIT_SHUFFLE); // x0: {bbbbcccc|aaaaaabb|ccdddddd|bbbbcccc} x8 (1021) if matches_isa!(S, SSE2) { let m1 = s.u32x8_splat(u32::from_le_bytes([0x00, 0xfc, 0xc0, 0x0f])); let x1 = s.v256_and(x0, m1); // x1: {00000000|aaaaaa00|cc000000|0000cccc} x8 let m2 = s.u32x8_splat(u32::from_le_bytes([0xf0, 0x03, 0x3f, 0x00])); let x2 = s.v256_and(x0, m2); // x2: {bbbb0000|000000bb|00dddddd|00000000} x8 let m3 = s.u32x8_splat(u32::from_le_bytes([0x40, 0x00, 0x00, 0x04])); let x3 = s.u16x16_mul_hi(x1, m3); // x3: {00aaaaaa|00000000|00cccccc|00000000} x8 let m4 = s.u32x8_splat(u32::from_le_bytes([0x10, 0x00, 0x00, 0x01])); let x4 = s.i16x16_mul_lo(x2, m4); // x4: {00000000|00bbbbbb|00000000|00dddddd} x8 return s.v256_or(x3, x4); // {00aaaaaa|00bbbbbb|00cccccc|00dddddd} x8 } if matches_isa!(S, NEON | WASM128) { let m1 = s.u32x8_splat(u32::from_le_bytes([0x00, 0xfc, 0x00, 0x00])); let x1 = s.u16x16_shr::<10>(s.v256_and(x0, m1)); // x1: {00aaaaaa|000000000|00000000|00000000} x8 let m2 = s.u32x8_splat(u32::from_le_bytes([0xf0, 0x03, 0x00, 0x00])); let x2 = s.u16x16_shl::<4>(s.v256_and(x0, m2)); // x2: {00000000|00bbbbbb|00000000|00000000} x8 let m3 = s.u32x8_splat(u32::from_le_bytes([0x00, 0x00, 0xc0, 0x0f])); let x3 = s.u16x16_shr::<6>(s.v256_and(x0, m3)); // x3: {00000000|00000000|00cccccc|00000000} x8 let m4 = s.u32x8_splat(u32::from_le_bytes([0x00, 0x00, 0x3f, 0x00])); let x4 = s.u16x16_shl::<8>(s.v256_and(x0, m4)); // x4: {00000000|00000000|00000000|00dddddd} x8 return s.v256_or(s.v256_or(x1, x2), s.v256_or(x3, x4)); // {00aaaaaa|00bbbbbb|00cccccc|00dddddd} x8 } unreachable!() } #[inline(always)] fn split_bits_x1(s: S, x: V128) -> V128 { // x: {AAAB|BBCC|CDDD|????} const SHUFFLE: V128 = SPLIT_SHUFFLE.to_v128x2().1; let x0 = s.u8x16_swizzle(x, SHUFFLE); // x0: {bbbbcccc|aaaaaabb|ccdddddd|bbbbcccc} x8 (1021) if matches_isa!(S, SSE2) { let m1 = s.u32x4_splat(u32::from_le_bytes([0x00, 0xfc, 0xc0, 0x0f])); let x1 = s.v128_and(x0, m1); let m2 = s.u32x4_splat(u32::from_le_bytes([0xf0, 0x03, 0x3f, 0x00])); let x2 = s.v128_and(x0, m2); let m3 = s.u32x4_splat(u32::from_le_bytes([0x40, 0x00, 0x00, 0x04])); let x3 = s.u16x8_mul_hi(x1, m3); let m4 = s.u32x4_splat(u32::from_le_bytes([0x10, 0x00, 0x00, 0x01])); let x4 = s.i16x8_mul_lo(x2, m4); return s.v128_or(x3, x4); } if matches_isa!(S, NEON | WASM128) { let m1 = s.u32x4_splat(u32::from_le_bytes([0x00, 0xfc, 0x00, 0x00])); let x1 = s.u16x8_shr::<10>(s.v128_and(x0, m1)); let m2 = s.u32x4_splat(u32::from_le_bytes([0xf0, 0x03, 0x00, 0x00])); let x2 = s.u16x8_shl::<4>(s.v128_and(x0, m2)); let m3 = s.u32x4_splat(u32::from_le_bytes([0x00, 0x00, 0xc0, 0x0f])); let x3 = s.u16x8_shr::<6>(s.v128_and(x0, m3)); let m4 = s.u32x4_splat(u32::from_le_bytes([0x00, 0x00, 0x3f, 0x00])); let x4 = s.u16x8_shl::<8>(s.v128_and(x0, m4)); return s.v128_or(s.v128_or(x1, x2), s.v128_or(x3, x4)); } unreachable!() } #[inline] const fn encoding_shift(charset: &'static [u8; 64]) -> V128 { // 0~25 'A' [13] // 26~51 'a' [0] // 52~61 '0' [1~10] // 62 c62 [11] // 63 c63 [12] let mut lut = [0x80; 16]; lut[13] = b'A'; lut[0] = b'a' - 26; let mut i = 1; while i <= 10 { lut[i] = b'0'.wrapping_sub(52); i += 1; } lut[11] = charset[62].wrapping_sub(62); lut[12] = charset[63].wrapping_sub(63); V128::from_bytes(lut) } const STANDARD_ENCODING_SHIFT: V128 = encoding_shift(STANDARD_CHARSET); const URL_SAFE_ENCODING_SHIFT: V128 = encoding_shift(URL_SAFE_CHARSET); const STANDARD_ENCODING_SHIFT_X2: V256 = STANDARD_ENCODING_SHIFT.x2(); const URL_SAFE_ENCODING_SHIFT_X2: V256 = URL_SAFE_ENCODING_SHIFT.x2(); #[inline(always)] fn encode_values, V: POD>(s: S, x: V, shift_lut: V) -> V { // x: {00aaaaaa|00bbbbbb|00cccccc|00dddddd} xn let x1 = s.u8xn_sub_sat(x, s.u8xn_splat(51)); // 0~25 => 0 // 26~51 => 0 // 52~61 => 1~10 // 62 => 11 // 63 => 12 let x2 = s.i8xn_lt(x, s.u8xn_splat(26)); let x3 = s.and(x2, s.u8xn_splat(13)); let x4 = s.or(x1, x3); // 0~25 => 0xff => 13 // 26~51 => 0 => 0 // 52~61 => 0 => 1~10 // 62 => 0 => 11 // 63 => 0 => 12 let shift = s.u8x16xn_swizzle(shift_lut, x4); s.u8xn_add(x, shift) // {{ascii}} xn } #[inline(always)] fn encode_bytes24(s: S, x: V256, shift_lut: V256) -> V256 { // x: {????|AAAB|BBCC|CDDD|EEEF|FFGG|GHHH|????} let values = split_bits_x2(s, x); // values: {00aaaaaa|00bbbbbb|00cccccc|00dddddd} x8 encode_values(s, values, shift_lut) // {{ascii}} x32 } #[inline(always)] fn encode_bytes12(s: S, x: V128, shift_lut: V128) -> V128 { // x: {AAAB|BBCC|CDDD|????} let values = split_bits_x1(s, x); // values: {00aaaaaa|00bbbbbb|00cccccc|00dddddd} x4 encode_values(s, values, shift_lut) // {{ascii}} x16 } base64-simd-0.8.0/src/error.rs000064400000000000000000000016401046102023000141360ustar 00000000000000use core::fmt; /// Base64 Error pub struct Error(()); impl Error { #[inline(always)] pub(crate) const fn new() -> Self { Error(()) } } impl fmt::Debug for Error { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ::fmt("Base64Error", f) } } impl fmt::Display for Error { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ::fmt("Base64Error", f) } } #[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[cfg(feature = "std")] impl std::error::Error for Error {} macro_rules! ensure { ($cond:expr) => { if !$cond { return Err($crate::error::Error::new()); } }; } #[allow(unused_macros)] macro_rules! try_ { ($result:expr) => { match $result { Ok(value) => value, Err(_) => return Err(Error::new()), } }; } base64-simd-0.8.0/src/forgiving.rs000064400000000000000000000101261046102023000147760ustar 00000000000000use crate::ascii::*; use crate::STANDARD_FORGIVING; use crate::{Error, Out}; use vsimd::tools::slice_mut; use core::ptr::copy_nonoverlapping; #[cfg(feature = "alloc")] use alloc::vec::Vec; /// Forgiving decodes a base64 string to bytes and writes inplace. /// /// This function uses the standard charset. /// /// See /// /// # Errors /// This function returns `Err` if the content of `data` is invalid. #[inline] pub fn forgiving_decode_inplace(data: &mut [u8]) -> Result<&mut [u8], Error> { let data = remove_ascii_whitespace_inplace(data); STANDARD_FORGIVING.decode_inplace(data) } /// Forgiving decodes a base64 string to bytes. /// /// This function uses the standard charset. /// /// See /// /// # Errors /// This function returns `Err` if the content of `src` is invalid. /// /// # Panics /// This function asserts that `src.len() <= dst.len()` #[inline] pub fn forgiving_decode<'d>(src: &[u8], mut dst: Out<'d, [u8]>) -> Result<&'d mut [u8], Error> { assert!(src.len() <= dst.len()); let pos = find_non_ascii_whitespace(src); debug_assert!(pos <= src.len()); if pos == src.len() { return STANDARD_FORGIVING.decode(src, dst); } unsafe { let len = src.len(); let src = src.as_ptr(); let dst = dst.as_mut_ptr(); copy_nonoverlapping(src, dst, pos); let rem = remove_ascii_whitespace_fallback(src.add(pos), len - pos, dst.add(pos)); debug_assert!(rem <= len - pos); let data = slice_mut(dst, pos + rem); STANDARD_FORGIVING.decode_inplace(data) } } /// Forgiving decodes a base64 string to bytes and returns a new [`Vec`](Vec). /// /// This function uses the standard charset. /// /// See /// /// # Errors /// This function returns `Err` if the content of `data` is invalid. #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] #[cfg(feature = "alloc")] #[inline] pub fn forgiving_decode_to_vec(data: &[u8]) -> Result, Error> { let pos = find_non_ascii_whitespace(data); debug_assert!(pos <= data.len()); if pos == data.len() { return STANDARD_FORGIVING.decode_type::>(data); } let mut vec = Vec::with_capacity(data.len()); unsafe { let len = data.len(); let src = data.as_ptr(); let dst = vec.as_mut_ptr(); copy_nonoverlapping(src, dst, pos); let rem = remove_ascii_whitespace_fallback(src.add(pos), len - pos, dst.add(pos)); debug_assert!(rem <= len - pos); let data = slice_mut(dst, pos + rem); let ans_len = STANDARD_FORGIVING.decode_inplace(data)?.len(); vec.set_len(ans_len); }; Ok(vec) } #[cfg(test)] mod tests { use super::*; use crate::AsOut; #[cfg_attr(not(target_arch = "wasm32"), test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn test_forgiving() { use const_str::hex; let mut inputs: Vec<&str> = Vec::new(); let mut outputs: Vec<&[u8]> = Vec::new(); { let mut i = |i| inputs.push(i); let mut o = |o| outputs.push(o); i("ab"); o(&[0x69]); i("abc"); o(&[0x69, 0xB7]); i("abcd"); o(&[0x69, 0xB7, 0x1D]); i("helloworld"); o(&hex!("85 E9 65 A3 0A 2B 95")); i(" h e l l o w o r\nl\rd\t"); o(&hex!("85 E9 65 A3 0A 2B 95")); } for i in 0..inputs.len() { let (src, expected) = (inputs[i], outputs[i]); let mut buf = src.to_owned().into_bytes(); let ans = forgiving_decode_inplace(&mut buf).unwrap(); assert_eq!(ans, expected); let ans = crate::forgiving_decode(src.as_bytes(), buf.as_out()).unwrap(); assert_eq!(ans, expected); #[cfg(feature = "alloc")] { let ans = crate::forgiving_decode_to_vec(src.as_bytes()).unwrap(); assert_eq!(ans, expected); } } } } base64-simd-0.8.0/src/heap.rs000064400000000000000000000112261046102023000137230ustar 00000000000000use crate::decode::decoded_length; use crate::encode::encoded_length_unchecked; use crate::{AppendBase64Decode, AppendBase64Encode}; use crate::{Base64, Error}; use crate::{FromBase64Decode, FromBase64Encode}; use vsimd::tools::{alloc_uninit_bytes, assume_init, boxed_str, slice_parts}; use alloc::boxed::Box; use alloc::string::String; use alloc::vec::Vec; #[inline] fn encode_to_boxed_str(base64: &Base64, data: &[u8]) -> Box { if data.is_empty() { return Box::from(""); } unsafe { let m = encoded_length_unchecked(data.len(), base64.config); assert!(m <= usize::MAX / 2); let mut buf = alloc_uninit_bytes(m); { let (src, len) = slice_parts(data); let dst: *mut u8 = buf.as_mut_ptr().cast(); crate::multiversion::encode::auto(src, len, dst, base64.config); } boxed_str(assume_init(buf)) } } #[inline] fn encode_append_vec(base64: &Base64, src: &[u8], buf: &mut Vec) { if src.is_empty() { return; } unsafe { let m = encoded_length_unchecked(src.len(), base64.config); assert!(m <= usize::MAX / 2); buf.reserve_exact(m); let prev_len = buf.len(); { let (src, len) = slice_parts(src); let dst = buf.as_mut_ptr().add(prev_len); crate::multiversion::encode::auto(src, len, dst, base64.config); } buf.set_len(prev_len + m); } } #[inline] fn decode_to_boxed_bytes(base64: &Base64, data: &[u8]) -> Result, Error> { if data.is_empty() { return Ok(Box::from([])); } unsafe { let (n, m) = decoded_length(data, base64.config)?; // safety: 0 < m < isize::MAX let mut buf = alloc_uninit_bytes(m); { let dst = buf.as_mut_ptr().cast(); let src = data.as_ptr(); crate::multiversion::decode::auto(src, dst, n, base64.config)?; } Ok(assume_init(buf)) } } #[inline] fn decode_append_vec(base64: &Base64, src: &[u8], buf: &mut Vec) -> Result<(), Error> { if src.is_empty() { return Ok(()); } unsafe { let (n, m) = decoded_length(src, base64.config)?; buf.reserve_exact(m); let prev_len = buf.len(); let dst = buf.as_mut_ptr().add(prev_len); let src = src.as_ptr(); crate::multiversion::decode::auto(src, dst, n, base64.config)?; buf.set_len(prev_len + m); Ok(()) } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl FromBase64Decode for Box<[u8]> { #[inline] fn from_base64_decode(base64: &Base64, data: &[u8]) -> Result { decode_to_boxed_bytes(base64, data) } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl FromBase64Decode for Vec { #[inline] fn from_base64_decode(base64: &Base64, data: &[u8]) -> Result { let ans = decode_to_boxed_bytes(base64, data)?; Ok(Vec::from(ans)) } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl FromBase64Encode for Box<[u8]> { #[inline] fn from_base64_encode(base64: &Base64, data: &[u8]) -> Self { let ans = encode_to_boxed_str(base64, data); ans.into_boxed_bytes() } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl FromBase64Encode for Box { #[inline] fn from_base64_encode(base64: &Base64, data: &[u8]) -> Self { encode_to_boxed_str(base64, data) } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl FromBase64Encode for Vec { #[inline] fn from_base64_encode(base64: &Base64, data: &[u8]) -> Self { let ans = encode_to_boxed_str(base64, data); Vec::from(ans.into_boxed_bytes()) } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl FromBase64Encode for String { #[inline] fn from_base64_encode(base64: &Base64, data: &[u8]) -> Self { let ans = encode_to_boxed_str(base64, data); String::from(ans) } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl AppendBase64Encode for Vec { #[inline] fn append_base64_encode(base64: &Base64, src: &[u8], dst: &mut Self) { encode_append_vec(base64, src, dst); } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl AppendBase64Encode for String { #[inline] fn append_base64_encode(base64: &Base64, src: &[u8], dst: &mut Self) { unsafe { encode_append_vec(base64, src, dst.as_mut_vec()) }; } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl AppendBase64Decode for Vec { #[inline] fn append_base64_decode(base64: &Base64, src: &[u8], dst: &mut Self) -> Result<(), Error> { decode_append_vec(base64, src, dst) } } base64-simd-0.8.0/src/lib.rs000064400000000000000000000242251046102023000135570ustar 00000000000000//! SIMD-accelerated base64 encoding and decoding. //! //! # Examples //! //! ``` //! # #[cfg(feature = "alloc")] //! # { //! let bytes = b"hello world"; //! let base64 = base64_simd::STANDARD; //! //! let encoded = base64.encode_to_string(bytes); //! assert_eq!(encoded, "aGVsbG8gd29ybGQ="); //! //! let decoded = base64.decode_to_vec(encoded).unwrap(); //! assert_eq!(decoded, bytes); //! # } //! ``` //! #![doc=vsimd::shared_docs!()] // #![cfg_attr(not(any(feature = "std", test)), no_std)] #![cfg_attr(feature = "unstable", feature(arm_target_feature))] #![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(test, deny(warnings))] // #![deny( missing_debug_implementations, missing_docs, clippy::all, clippy::pedantic, clippy::cargo, clippy::missing_inline_in_public_items )] #![warn(clippy::todo)] #![allow( clippy::inline_always, clippy::wildcard_imports, clippy::module_name_repetitions, clippy::cast_sign_loss, clippy::cast_possible_truncation, clippy::cast_lossless, clippy::cast_possible_wrap, clippy::items_after_statements, clippy::match_same_arms, clippy::verbose_bit_mask )] #[cfg(feature = "alloc")] extern crate alloc; #[macro_use] mod error; pub use self::error::Error; mod alsw; mod ascii; mod check; mod decode; mod encode; mod multiversion; #[cfg(feature = "alloc")] mod heap; mod forgiving; pub use self::forgiving::*; pub use outref::{AsOut, Out}; // ----------------------------------------------------------------------------- use crate::decode::decoded_length; use crate::encode::encoded_length_unchecked; use vsimd::tools::{slice_mut, slice_parts}; #[cfg(feature = "alloc")] use alloc::{string::String, vec::Vec}; const STANDARD_CHARSET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const URL_SAFE_CHARSET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; /// Base64 variant #[derive(Debug)] pub struct Base64 { config: Config, } #[derive(Debug, Clone, Copy)] enum Kind { Standard, UrlSafe, } #[derive(Debug, Clone, Copy)] struct Config { kind: Kind, extra: Extra, } #[derive(Debug, Clone, Copy)] enum Extra { Pad, NoPad, Forgiving, } impl Extra { /// Whether to add padding when encoding #[inline(always)] #[must_use] const fn padding(self) -> bool { match self { Extra::Pad => true, Extra::NoPad => false, Extra::Forgiving => true, } } #[inline(always)] #[must_use] const fn forgiving(self) -> bool { match self { Extra::Pad => false, Extra::NoPad => false, Extra::Forgiving => true, } } } /// Standard charset with padding. pub const STANDARD: Base64 = Base64 { config: Config { kind: Kind::Standard, extra: Extra::Pad, }, }; /// URL-Safe charset with padding. pub const URL_SAFE: Base64 = Base64 { config: Config { kind: Kind::UrlSafe, extra: Extra::Pad, }, }; /// Standard charset without padding. pub const STANDARD_NO_PAD: Base64 = Base64 { config: Config { kind: Kind::Standard, extra: Extra::NoPad, }, }; /// URL-Safe charset without padding. pub const URL_SAFE_NO_PAD: Base64 = Base64 { config: Config { kind: Kind::UrlSafe, extra: Extra::NoPad, }, }; const STANDARD_FORGIVING: Base64 = Base64 { config: Config { kind: Kind::Standard, extra: Extra::Forgiving, }, }; impl Base64 { /// Returns the character set. #[inline] #[must_use] pub const fn charset(&self) -> &[u8; 64] { match self.config.kind { Kind::Standard => STANDARD_CHARSET, Kind::UrlSafe => URL_SAFE_CHARSET, } } /// Calculates the encoded length. /// /// # Panics /// This function will panic if `n > isize::MAX`. #[inline] #[must_use] pub const fn encoded_length(&self, n: usize) -> usize { assert!(n <= usize::MAX / 2); encoded_length_unchecked(n, self.config) } /// Estimates the decoded length. /// /// The result is an upper bound which can be used for allocation. #[inline] #[must_use] pub const fn estimated_decoded_length(&self, n: usize) -> usize { if n % 4 == 0 { n / 4 * 3 } else { (n / 4 + 1) * 3 } } /// Calculates the decoded length. /// /// The result is a precise value which can be used for allocation. /// /// # Errors /// This function returns `Err` if the content of `data` is partially invalid. #[inline] pub fn decoded_length(&self, data: &[u8]) -> Result { let (_, m) = decoded_length(data, self.config)?; Ok(m) } /// Checks whether `data` is a base64 string. /// /// # Errors /// This function returns `Err` if the content of `data` is invalid. #[inline] pub fn check(&self, data: &[u8]) -> Result<(), Error> { let (n, _) = decoded_length(data, self.config)?; unsafe { crate::multiversion::check::auto(data.as_ptr(), n, self.config) } } /// Encodes bytes to a base64 string. /// /// # Panics /// This function will panic if the length of `dst` is not enough. #[inline] #[must_use] pub fn encode<'d>(&self, src: &[u8], mut dst: Out<'d, [u8]>) -> &'d mut [u8] { unsafe { let m = encoded_length_unchecked(src.len(), self.config); assert!(dst.len() >= m); let (src, len) = slice_parts(src); let dst = dst.as_mut_ptr(); self::multiversion::encode::auto(src, len, dst, self.config); slice_mut(dst, m) } } /// Encodes bytes to a base64 string and returns [`&mut str`](str). /// /// # Panics /// This function will panic if the length of `dst` is not enough. #[inline] #[must_use] pub fn encode_as_str<'d>(&self, src: &[u8], dst: Out<'d, [u8]>) -> &'d mut str { let ans = self.encode(src, dst); unsafe { core::str::from_utf8_unchecked_mut(ans) } } /// Decodes a base64 string to bytes. /// /// # Errors /// This function returns `Err` if the content of `src` is invalid. /// /// # Panics /// This function will panic if the length of `dst` is not enough. #[inline] pub fn decode<'d>(&self, src: &[u8], mut dst: Out<'d, [u8]>) -> Result<&'d mut [u8], Error> { unsafe { let (n, m) = decoded_length(src, self.config)?; assert!(dst.len() >= m); let src = src.as_ptr(); let dst = dst.as_mut_ptr(); self::multiversion::decode::auto(src, dst, n, self.config)?; Ok(slice_mut(dst, m)) } } /// Decodes a base64 string to bytes and writes inplace. /// /// # Errors /// This function returns `Err` if the content of `data` is invalid. #[inline] pub fn decode_inplace<'d>(&self, data: &'d mut [u8]) -> Result<&'d mut [u8], Error> { unsafe { let (n, m) = decoded_length(data, self.config)?; let dst: *mut u8 = data.as_mut_ptr(); let src: *const u8 = dst; self::multiversion::decode::auto(src, dst, n, self.config)?; Ok(slice_mut(dst, m)) } } /// Encodes bytes to a base64 string and returns a specified type. #[inline] #[must_use] pub fn encode_type(&self, data: impl AsRef<[u8]>) -> T { T::from_base64_encode(self, data.as_ref()) } /// Decodes a base64 string to bytes and returns a specified type. /// /// # Errors /// This function returns `Err` if the content of `data` is invalid. #[inline] pub fn decode_type(&self, data: impl AsRef<[u8]>) -> Result { T::from_base64_decode(self, data.as_ref()) } /// Encodes bytes to a base64 string and appends to a specified type. #[inline] pub fn encode_append(&self, src: impl AsRef<[u8]>, dst: &mut T) { T::append_base64_encode(self, src.as_ref(), dst); } /// Decodes a base64 string to bytes and appends to a specified type. /// /// # Errors /// This function returns `Err` if the content of `src` is invalid. #[inline] pub fn decode_append(&self, src: impl AsRef<[u8]>, dst: &mut T) -> Result<(), Error> { T::append_base64_decode(self, src.as_ref(), dst) } /// Encodes bytes to a base64 string. #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] #[cfg(feature = "alloc")] #[inline] #[must_use] pub fn encode_to_string(&self, data: impl AsRef<[u8]>) -> String { self.encode_type(data) } /// Decodes a base64 string to bytes. /// /// # Errors /// This function returns `Err` if the content of `data` is invalid. #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] #[cfg(feature = "alloc")] #[inline] pub fn decode_to_vec(&self, data: impl AsRef<[u8]>) -> Result, Error> { self.decode_type(data) } } /// Types that can represent a base64 string. pub trait FromBase64Encode: Sized { /// Encodes bytes to a base64 string and returns the self type. fn from_base64_encode(base64: &Base64, data: &[u8]) -> Self; } /// Types that can be decoded from a base64 string. pub trait FromBase64Decode: Sized { /// Decodes a base64 string to bytes and returns the self type. /// /// # Errors /// This function returns `Err` if the content of `data` is invalid. fn from_base64_decode(base64: &Base64, data: &[u8]) -> Result; } /// Types that can append a base64 string. pub trait AppendBase64Encode: FromBase64Encode { /// Encodes bytes to a base64 string and appends into the self type. fn append_base64_encode(base64: &Base64, src: &[u8], dst: &mut Self); } /// Types that can append bytes decoded from from a base64 string. pub trait AppendBase64Decode: FromBase64Decode { /// Decodes a base64 string to bytes and appends to the self type. /// /// # Errors /// This function returns `Err` if the content of `src` is invalid. fn append_base64_decode(base64: &Base64, src: &[u8], dst: &mut Self) -> Result<(), Error>; } base64-simd-0.8.0/src/multiversion.rs000064400000000000000000000027231046102023000155500ustar 00000000000000use crate::{Config, Error}; vsimd::dispatch!( name = {encode}, signature = {pub(crate) unsafe fn(src: *const u8, len: usize, dst: *mut u8, config: Config) -> ()}, fallback = {crate::encode::encode_fallback}, simd = {crate::encode::encode_simd}, targets = {"avx2", "ssse3", "neon", "simd128"}, fastest = {"avx2", "neon", "simd128"}, ); vsimd::dispatch!( name = {decode}, signature = {pub(crate) unsafe fn(src: *const u8, dst: *mut u8, n: usize, config: Config) -> Result<(), Error>}, fallback = {crate::decode::decode_fallback}, simd = {crate::decode::decode_simd}, targets = {"avx2", "ssse3", "neon", "simd128"}, fastest = {"avx2", "neon", "simd128"}, ); vsimd::dispatch!( name = {check}, signature = {pub(crate) unsafe fn(src: *const u8, n: usize, config: Config) -> Result<(), Error>}, fallback = {crate::check::check_fallback}, simd = {crate::check::check_simd}, targets = {"avx2", "ssse3", "neon", "simd128"}, fastest = {"avx2", "neon", "simd128"}, ); vsimd::dispatch!( name = {find_non_ascii_whitespace}, signature = {pub unsafe fn(src: *const u8, len: usize) -> usize}, fallback = {crate::ascii::find_non_ascii_whitespace_fallback}, simd = {crate::ascii::find_non_ascii_whitespace_simd}, targets = {"avx2", "sse2", "neon", "simd128"}, fastest = {"avx2", "neon", "simd128"}, ); base64-simd-0.8.0/tests/it.rs000064400000000000000000000111761046102023000140010ustar 00000000000000use base64_simd::{AsOut, Base64}; use base64_simd::{STANDARD, STANDARD_NO_PAD, URL_SAFE, URL_SAFE_NO_PAD}; fn rand_bytes(n: usize) -> Vec { use rand::RngCore; let mut bytes = vec![0u8; n]; rand::thread_rng().fill_bytes(&mut bytes); bytes } #[cfg(miri)] use std::io::Write as _; macro_rules! dbgmsg { ($($fmt:tt)*) => { // println!($($fmt)*); // #[cfg(miri)] // std::io::stdout().flush().unwrap(); }; } #[cfg_attr(not(target_arch = "wasm32"), test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn basic() { let cases: &[(Base64, &str, &str)] = &[ (STANDARD, "", ""), (STANDARD, "f", "Zg=="), (STANDARD, "fo", "Zm8="), (STANDARD, "foo", "Zm9v"), (STANDARD, "foob", "Zm9vYg=="), (STANDARD, "fooba", "Zm9vYmE="), (STANDARD, "foobar", "Zm9vYmFy"), ]; let mut buf: Vec = Vec::new(); for &(ref base64, input, output) in cases { buf.clear(); buf.resize(base64.encoded_length(input.len()), 0); let ans = base64.encode_as_str(input.as_bytes(), buf.as_out()); assert_eq!(ans, output); buf.clear(); buf.resize(base64.decoded_length(output.as_bytes()).unwrap(), 0); let ans = base64.decode(output.as_bytes(), buf.as_out()).unwrap(); assert_eq!(ans, input.as_bytes()); } } #[cfg(feature = "alloc")] #[cfg_attr(not(target_arch = "wasm32"), test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn allocation() { let src = "helloworld"; let prefix = "data:;base64,"; let mut encode_buf = prefix.to_owned(); STANDARD.encode_append(src, &mut encode_buf); assert_eq!(encode_buf, format!("{prefix}aGVsbG93b3JsZA==")); let mut decode_buf = b"123".to_vec(); let src = &encode_buf[prefix.len()..]; STANDARD.decode_append(src, &mut decode_buf).unwrap(); assert_eq!(decode_buf, b"123helloworld"); } #[cfg_attr(not(target_arch = "wasm32"), test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn random() { dbgmsg!(); for n in 0..128 { dbgmsg!("n = {}", n); let bytes = rand_bytes(n); let test_config = [ STANDARD, // URL_SAFE, // STANDARD_NO_PAD, // URL_SAFE_NO_PAD, // ]; let base_config = { use base64::alphabet::*; use base64::engine::fast_portable::{FastPortable, NO_PAD, PAD}; let f = FastPortable::from; [ f(&STANDARD, PAD), f(&URL_SAFE, PAD), f(&STANDARD, NO_PAD), f(&URL_SAFE, NO_PAD), ] }; for (base64, config) in test_config.into_iter().zip(base_config.into_iter()) { dbgmsg!("base64 = {:?}", base64); let encoded = base64::encode_engine(&bytes, &config); let encoded = encoded.as_bytes(); assert!(base64.check(encoded).is_ok()); { let mut buf = vec![0u8; base64.encoded_length(n)]; let ans = base64.encode(&bytes, buf.as_out()); assert_eq!(ans, encoded); assert!(base64.check(ans).is_ok()); dbgmsg!("encoding ... ok"); } { let mut buf = encoded.to_owned(); let ans = base64.decode_inplace(&mut buf).unwrap(); assert_eq!(ans, bytes); dbgmsg!("decoding inplace ... ok"); } { let mut buf = vec![0u8; n]; let ans = base64.decode(encoded, buf.as_out()).unwrap(); assert_eq!(ans, bytes); dbgmsg!("decoding ... ok"); } } } } /// #[cfg_attr(not(target_arch = "wasm32"), test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn canonicity() { let test_vectors = [ ("SGVsbG8=", Some("Hello")), ("SGVsbG9=", None), ("SGVsbG9", None), ("SGVsbA==", Some("Hell")), ("SGVsbA=", None), ("SGVsbA", None), ("SGVsbA====", None), ]; let mut buf = [0u8; 64]; for (encoded, expected) in test_vectors { let base64 = STANDARD; let is_valid = base64.check(encoded.as_bytes()).is_ok(); let result: _ = base64.decode(encoded.as_bytes(), buf.as_mut_slice().as_out()); assert_eq!(is_valid, result.is_ok()); match expected { Some(expected) => assert_eq!(result.unwrap(), expected.as_bytes()), None => assert!(result.is_err()), } } }