hex-simd-0.8.0/.cargo_vcs_info.json0000644000000001550000000000100125600ustar { "git": { "sha1": "d74c030d9dc4f3cae02146d1f497ff62726ef09a" }, "path_in_vcs": "crates/hex-simd" }hex-simd-0.8.0/Cargo.toml0000644000000025630000000000100105630ustar # 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 = "hex-simd" version = "0.8.0" description = "SIMD-accelerated hex encoding and decoding" readme = "README.md" keywords = [ "hex", "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.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" hex-simd-0.8.0/Cargo.toml.orig000064400000000000000000000014421046102023000142370ustar 00000000000000[package] name = "hex-simd" version = "0.8.0" edition = "2021" description = "SIMD-accelerated hex encoding and decoding" license = "MIT" repository = "https://github.com/Nugine/simd" keywords = ["hex", "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] rand = "0.8.5" [target.'cfg(target_arch="wasm32")'.dev-dependencies] getrandom = { version = "0.2.8", features = ["js"] } wasm-bindgen-test = "0.3.33" hex-simd-0.8.0/README.md000064400000000000000000000006721046102023000126330ustar 00000000000000# hex-simd [![Crates.io](https://img.shields.io/crates/v/hex-simd.svg)](https://crates.io/crates/hex-simd) [![Docs](https://docs.rs/hex-simd/badge.svg)](https://docs.rs/hex-simd/) [![MIT licensed][mit-badge]][mit-url] [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg [mit-url]: ../../LICENSE SIMD-accelerated hex encoding and decoding. Documentation: Repository: hex-simd-0.8.0/src/check.rs000064400000000000000000000040271046102023000135640ustar 00000000000000use crate::Error; use vsimd::hex::unhex; use vsimd::isa::{AVX2, WASM128}; use vsimd::{matches_isa, SIMD256}; #[inline(always)] unsafe fn check_short(mut src: *const u8, len: usize) -> Result<(), Error> { let mut flag = 0; let end = src.add(len); while src < end { let x = src.read(); flag |= unhex(x); src = src.add(1); } ensure!(flag != 0xff); Ok(()) } /// FIXME: work around for suboptimal auto-vectorization (AVX2, WASM128) #[inline(always)] unsafe fn check_short_sc(mut src: *const u8, len: usize) -> Result<(), Error> { let end = src.add(len); while src < end { let x = src.read(); ensure!(unhex(x) != 0xff); src = src.add(1); } Ok(()) } #[inline(always)] pub unsafe fn check_fallback(src: *const u8, len: usize) -> Result<(), Error> { check_short(src, len) } #[inline(always)] pub unsafe fn check_simd(s: S, mut src: *const u8, mut len: usize) -> Result<(), Error> { if matches_isa!(S, AVX2) { if len == 16 { let x = s.v128_load_unaligned(src); ensure!(vsimd::hex::check_xn(s, x)); return Ok(()); } let end = src.add(len / 32 * 32); while src < end { let x = s.v256_load_unaligned(src); ensure!(vsimd::hex::check_xn(s, x)); src = src.add(32); } len %= 32; if len == 0 { return Ok(()); } if len >= 16 { let x = s.v128_load_unaligned(src); ensure!(vsimd::hex::check_xn(s, x)); len -= 16; src = src.add(16); } } else { let end = src.add(len / 16 * 16); while src < end { let x = s.v128_load_unaligned(src); ensure!(vsimd::hex::check_xn(s, x)); src = src.add(16); } len %= 16; if len == 0 { return Ok(()); } } if matches_isa!(S, AVX2 | WASM128) { check_short_sc(src, len) } else { check_short(src, len) } } hex-simd-0.8.0/src/decode.rs000064400000000000000000000116421046102023000137330ustar 00000000000000use crate::Error; use vsimd::hex::unhex; use vsimd::is_isa_type; use vsimd::isa::{Fallback, InstructionSet, AVX2, SSE2, WASM128}; use vsimd::matches_isa; use vsimd::tools::read; use vsimd::vector::V64; use vsimd::{SIMD128, SIMD256}; #[inline(always)] fn shl4(x: u8) -> u8 { x.wrapping_shl(4) } #[inline(always)] unsafe fn decode_bits(src: *const u8, dst: *mut u8) -> u8 { let y1 = unhex(read(src, 0)); let y2 = unhex(read(src, 1)); let z = shl4(y1) | y2; dst.write(z); y1 | y2 } #[inline(always)] unsafe fn decode_short(mut src: *const u8, len: usize, mut dst: *mut u8) -> Result<(), Error> where S: InstructionSet, { // FIXME: work around for suboptimal auto-vectorization (AVX2, WASM128) if matches_isa!(S, AVX2 | WASM128) { let end = src.add(len); while src < end { let flag = decode_bits(src, dst); ensure!(flag != 0xff); src = src.add(2); dst = dst.add(1); } Ok(()) } else { let end = src.add(len); let mut flag = 0; while src < end { flag |= decode_bits(src, dst); src = src.add(2); dst = dst.add(1); } ensure!(flag != 0xff); Ok(()) } } #[inline(always)] unsafe fn decode_long(mut src: *const u8, len: usize, mut dst: *mut u8) -> Result<(), Error> { let end = src.add(len / 16 * 16); while src < end { let mut flag = 0; let mut i = 0; while i < 8 { flag |= decode_bits(src, dst); src = src.add(2); dst = dst.add(1); i += 1; } ensure!(flag != 0xff); } decode_short::(src, len % 16, dst) } #[inline(always)] pub unsafe fn decode_fallback(src: *const u8, len: usize, dst: *mut u8) -> Result<(), Error> { decode_long(src, len, dst) } #[inline(always)] unsafe fn decode16(s: S, src: *const u8, dst: *mut u8) -> Result<(), Error> { let x = s.v128_load_unaligned(src); let y = try_!(vsimd::hex::decode_ascii16(s, x)); dst.cast::().write_unaligned(y); Ok(()) } #[inline(always)] unsafe fn decode32(s: S, src: *const u8, dst: *mut u8) -> Result<(), Error> { let x = s.v256_load_unaligned(src); let y = try_!(vsimd::hex::decode_ascii32(s, x)); s.v128_store_unaligned(dst, y); Ok(()) } #[inline(always)] pub unsafe fn decode_simd(s: S, src: *const u8, len: usize, dst: *mut u8) -> Result<(), Error> { if cfg!(any(target_arch = "x86", target_arch = "x86_64")) { if is_isa_type!(S, SSE2) { return decode_simd_sse2(SSE2::new(), src, len, dst); } if matches_isa!(S, AVX2) { return decode_simd_v256(s, src, len, dst); } } decode_simd_v128(s, src, len, dst) } #[inline(always)] pub unsafe fn decode_simd_v256( s: S, mut src: *const u8, mut len: usize, mut dst: *mut u8, ) -> Result<(), Error> { if len == 16 { return decode16(s, src, dst); } if len == 32 { return decode32(s, src, dst); } let end = src.add(len / 64 * 64); while src < end { let x0 = s.v256_load_unaligned(src); src = src.add(32); let x1 = s.v256_load_unaligned(src); src = src.add(32); let x = (x0, x1); let y = try_!(vsimd::hex::decode_ascii32x2(s, x)); s.v256_store_unaligned(dst, y); dst = dst.add(32); } len %= 64; if len == 0 { return Ok(()); } if len >= 32 { decode32(s, src, dst)?; src = src.add(32); dst = dst.add(16); len -= 32; } if len >= 16 { decode16(s, src, dst)?; src = src.add(16); dst = dst.add(8); len -= 16; } decode_short::(src, len, dst) } #[inline(always)] pub unsafe fn decode_simd_v128( s: S, mut src: *const u8, mut len: usize, mut dst: *mut u8, ) -> Result<(), Error> { let end = src.add(len / 32 * 32); while src < end { decode32(s, src, dst)?; src = src.add(32); dst = dst.add(16); } len %= 32; if len == 0 { return Ok(()); } if len >= 16 { decode16(s, src, dst)?; src = src.add(16); dst = dst.add(8); len -= 16; } decode_short::(src, len, dst) } #[inline(always)] pub unsafe fn decode_simd_sse2(s: SSE2, mut src: *const u8, mut len: usize, mut dst: *mut u8) -> Result<(), Error> { let end = src.add(len / 16 * 16); while src < end { let x = s.v128_load_unaligned(src); let (nibbles, flag) = vsimd::hex::sse2::decode_nibbles(s, x); ensure!(s.u8x16_bitmask(flag) == 0); let ans = vsimd::hex::sse2::merge_bits(s, nibbles); dst.cast::().write_unaligned(ans); src = src.add(16); dst = dst.add(8); } len %= 16; if len == 0 { return Ok(()); } decode_short::(src, len, dst) } hex-simd-0.8.0/src/encode.rs000064400000000000000000000111501046102023000137370ustar 00000000000000use vsimd::ascii::AsciiCase; use vsimd::is_isa_type; use vsimd::isa::{InstructionSet, AVX2, SSE2}; use vsimd::matches_isa; use vsimd::tools::{read, write}; use vsimd::{SIMD128, SIMD256}; #[inline(always)] fn charset(case: AsciiCase) -> &'static [u8; 16] { match case { AsciiCase::Lower => vsimd::hex::LOWER_CHARSET, AsciiCase::Upper => vsimd::hex::UPPER_CHARSET, } } #[inline(always)] unsafe fn encode_bits(src: *const u8, dst: *mut u8, charset: *const u8) { let x = src.read(); let hi = read(charset, (x >> 4) as usize); let lo = read(charset, (x & 0x0f) as usize); write(dst, 0, hi); write(dst, 1, lo); } #[inline(always)] unsafe fn encode_short(mut src: *const u8, len: usize, mut dst: *mut u8, charset: *const u8) { let end = src.add(len); while src < end { encode_bits(src, dst, charset); src = src.add(1); dst = dst.add(2); } } #[inline(always)] unsafe fn encode_long(mut src: *const u8, len: usize, mut dst: *mut u8, case: AsciiCase) { let charset = charset(case).as_ptr(); let end = src.add(len / 8 * 8); while src < end { let mut i = 0; while i < 8 { encode_bits(src, dst, charset); src = src.add(1); dst = dst.add(2); i += 1; } } encode_short(src, len % 8, dst, charset); } #[inline(always)] pub unsafe fn encode_fallback(src: *const u8, len: usize, dst: *mut u8, case: AsciiCase) { encode_long(src, len, dst, case); } #[inline(always)] pub unsafe fn encode_simd(s: S, src: *const u8, len: usize, dst: *mut u8, case: AsciiCase) { if cfg!(any(target_arch = "x86", target_arch = "x86_64")) { if is_isa_type!(S, SSE2) { return encode_simd_sse2(SSE2::new(), src, len, dst, case); } if matches_isa!(S, AVX2) { return encode_simd_v256(s, src, len, dst, case); } } encode_simd_v128(s, src, len, dst, case); } #[inline(always)] pub unsafe fn encode_simd_v256( s: S, mut src: *const u8, mut len: usize, mut dst: *mut u8, case: AsciiCase, ) { let lut = match case { AsciiCase::Lower => vsimd::hex::ENCODE_LOWER_LUT, AsciiCase::Upper => vsimd::hex::ENCODE_UPPER_LUT, }; if len == 16 { let x = s.v128_load_unaligned(src); let y = vsimd::hex::encode_bytes16(s, x, lut); s.v256_store_unaligned(dst, y); return; } let end = src.add(len / 32 * 32); while src < end { let x = s.v256_load_unaligned(src); let (y1, y2) = vsimd::hex::encode_bytes32(s, x, lut); s.v256_store_unaligned(dst, y1); dst = dst.add(32); s.v256_store_unaligned(dst, y2); dst = dst.add(32); src = src.add(32); } len %= 32; if len == 0 { return; } if len >= 16 { let x = s.v128_load_unaligned(src); let y = vsimd::hex::encode_bytes16(s, x, lut); s.v256_store_unaligned(dst, y); dst = dst.add(32); src = src.add(16); len -= 16; } if len > 0 { let charset = charset(case).as_ptr(); encode_short(src, len, dst, charset); } } #[inline(always)] pub unsafe fn encode_simd_v128( s: S, mut src: *const u8, mut len: usize, mut dst: *mut u8, case: AsciiCase, ) { let lut = match case { AsciiCase::Lower => vsimd::hex::ENCODE_LOWER_LUT, AsciiCase::Upper => vsimd::hex::ENCODE_UPPER_LUT, }; let end = src.add(len / 16 * 16); while src < end { let x = s.v128_load_unaligned(src); let y = vsimd::hex::encode_bytes16(s, x, lut); s.v256_store_unaligned(dst, y); dst = dst.add(32); src = src.add(16); } len %= 16; if len == 0 { return; } let charset = charset(case).as_ptr(); encode_short(src, len, dst, charset); } #[inline(always)] pub unsafe fn encode_simd_sse2(s: SSE2, mut src: *const u8, mut len: usize, mut dst: *mut u8, case: AsciiCase) { let offset = match case { AsciiCase::Lower => vsimd::hex::sse2::LOWER_OFFSET, AsciiCase::Upper => vsimd::hex::sse2::UPPER_OFFSET, }; let end = src.add(len / 16 * 16); while src < end { let x = s.v128_load_unaligned(src); src = src.add(16); let (y1, y2) = vsimd::hex::sse2::encode16(s, x, offset); s.v128_store_unaligned(dst, y1); dst = dst.add(16); s.v128_store_unaligned(dst, y2); dst = dst.add(16); } len %= 16; if len == 0 { return; } let charset = charset(case).as_ptr(); encode_short(src, len, dst, charset); } hex-simd-0.8.0/src/error.rs000064400000000000000000000016271046102023000136430ustar 00000000000000use core::fmt; /// Hex 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("HexError", f) } } impl fmt::Display for Error { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ::fmt("HexError", 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()), } }; } hex-simd-0.8.0/src/heap.rs000064400000000000000000000103641046102023000134250ustar 00000000000000use crate::{AppendHexDecode, AppendHexEncode, AsciiCase, Error, FromHexDecode, FromHexEncode}; 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 decode_to_boxed_bytes(src: &[u8]) -> Result, Error> { if src.is_empty() { return Ok(Box::from([])); } ensure!(src.len() % 2 == 0); unsafe { let mut buf = alloc_uninit_bytes(src.len() / 2); { let (src, len) = slice_parts(src); let dst: *mut u8 = buf.as_mut_ptr().cast(); crate::multiversion::decode::auto(src, len, dst)?; } Ok(assume_init(buf)) } } #[inline] fn decode_append_vec(src: &[u8], buf: &mut Vec) -> Result<(), Error> { if src.is_empty() { return Ok(()); } ensure!(src.len() % 2 == 0); let m = src.len() / 2; buf.reserve_exact(m); let prev_len = buf.len(); unsafe { let (src, len) = slice_parts(src); let dst = buf.as_mut_ptr().add(prev_len); crate::multiversion::decode::auto(src, len, dst)?; buf.set_len(prev_len + m); Ok(()) } } #[inline] fn encode_to_boxed_str(src: &[u8], case: AsciiCase) -> Box { if src.is_empty() { return Box::from(""); } unsafe { let m = src.len() * 2; assert!(m <= usize::MAX / 2); let mut buf = alloc_uninit_bytes(m); { let (src, len) = slice_parts(src); let dst: *mut u8 = buf.as_mut_ptr().cast(); crate::multiversion::encode::auto(src, len, dst, case); } boxed_str(assume_init(buf)) } } #[inline] fn encode_append_vec(src: &[u8], buf: &mut Vec, case: AsciiCase) { if src.is_empty() { return; } unsafe { let m = src.len() * 2; 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, case); buf.set_len(prev_len + m); } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl FromHexDecode for Box<[u8]> { #[inline] fn from_hex_decode(data: &[u8]) -> Result { decode_to_boxed_bytes(data) } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl FromHexDecode for Vec { #[inline] fn from_hex_decode(data: &[u8]) -> Result { let ans = decode_to_boxed_bytes(data)?; Ok(Vec::from(ans)) } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl FromHexEncode for Box<[u8]> { #[inline] #[must_use] fn from_hex_encode(data: &[u8], case: AsciiCase) -> Self { let ans = encode_to_boxed_str(data, case); ans.into_boxed_bytes() } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl FromHexEncode for Box { #[inline] #[must_use] fn from_hex_encode(data: &[u8], case: AsciiCase) -> Self { encode_to_boxed_str(data, case) } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl FromHexEncode for Vec { #[inline] #[must_use] fn from_hex_encode(data: &[u8], case: AsciiCase) -> Self { let ans = encode_to_boxed_str(data, case); Vec::from(ans.into_boxed_bytes()) } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl FromHexEncode for String { #[inline] #[must_use] fn from_hex_encode(data: &[u8], case: AsciiCase) -> Self { let ans = encode_to_boxed_str(data, case); String::from(ans) } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl AppendHexEncode for Vec { #[inline] fn append_hex_encode(src: &[u8], dst: &mut Self, case: AsciiCase) { encode_append_vec(src, dst, case); } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl AppendHexEncode for String { #[inline] fn append_hex_encode(src: &[u8], dst: &mut Self, case: AsciiCase) { unsafe { encode_append_vec(src, dst.as_mut_vec(), case) } } } #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl AppendHexDecode for Vec { #[inline] fn append_hex_decode(src: &[u8], dst: &mut Self) -> Result<(), Error> { decode_append_vec(src, dst) } } hex-simd-0.8.0/src/lib.rs000064400000000000000000000155641046102023000132650ustar 00000000000000//! SIMD-accelerated hex encoding and decoding. //! //! # Examples //! //! ``` //! # #[cfg(feature = "alloc")] //! # { //! use hex_simd::AsciiCase; //! //! let bytes = b"Hello world!"; //! //! let encoded = hex_simd::encode_to_string(bytes, AsciiCase::Lower); //! assert_eq!(encoded, "48656c6c6f20776f726c6421"); //! //! let decoded = hex_simd::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::cast_possible_wrap, clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::module_name_repetitions )] #[cfg(feature = "alloc")] extern crate alloc; #[macro_use] mod error; pub use self::error::Error; mod check; mod decode; mod encode; mod multiversion; #[cfg(feature = "alloc")] mod heap; pub use outref::{AsOut, Out}; pub use vsimd::ascii::AsciiCase; // ------------------------------------------------------------------------------------------------- use vsimd::tools::{slice_mut, slice_parts}; #[cfg(feature = "alloc")] use alloc::{string::String, vec::Vec}; /// Calculates the encoded length. /// /// # Panics /// This function will panic if `n > isize::MAX`. #[inline] #[must_use] pub const fn encoded_length(n: usize) -> usize { assert!(n <= usize::MAX / 2); n * 2 } /// Calculates the decoded length. /// /// # Errors /// This function returns `Err` if `n` is not even. #[inline] pub fn decoded_length(n: usize) -> Result { ensure!(n % 2 == 0); Ok(n / 2) } /// Checks whether `data` is a hex string. /// /// # Errors /// This function returns `Err` if the content of `data` is invalid. #[inline] pub fn check(data: &[u8]) -> Result<(), Error> { let (src, len) = slice_parts(data); unsafe { crate::multiversion::check::auto(src, len) } } /// Encodes bytes to a hex string. /// /// `case` specifies the ascii case of output. /// /// # Panics /// This function will panic if the length of `dst` is not enough. #[inline] #[must_use] pub fn encode<'d>(src: &[u8], mut dst: Out<'d, [u8]>, case: AsciiCase) -> &'d mut [u8] { assert!(dst.len() / 2 >= src.len()); unsafe { let (src, len) = slice_parts(src); let dst = dst.as_mut_ptr(); crate::multiversion::encode::auto(src, len, dst, case); slice_mut(dst, len * 2) } } /// Decodes a hex string to bytes case-insensitively. /// /// # 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>(src: &[u8], mut dst: Out<'d, [u8]>) -> Result<&'d mut [u8], Error> { ensure!(src.len() % 2 == 0); assert!(dst.len() >= src.len() / 2); let len = src.len(); let dst = dst.as_mut_ptr(); let src = src.as_ptr(); unsafe { crate::multiversion::decode::auto(src, len, dst)?; Ok(slice_mut(dst, len / 2)) } } /// Decodes a hex string to bytes case-insensitively and writes inplace. /// /// # Errors /// This function returns `Err` if the content of `data` is invalid. #[inline] pub fn decode_inplace(data: &mut [u8]) -> Result<&mut [u8], Error> { ensure!(data.len() % 2 == 0); unsafe { let len = data.len(); let dst: *mut u8 = data.as_mut_ptr(); let src: *const u8 = dst; crate::multiversion::decode::auto(src, len, dst)?; Ok(slice_mut(dst, len / 2)) } } /// Encodes bytes to a hex string and returns [`&mut str`](str). /// /// `case` specifies the ascii case of output. /// /// # Panics /// This function will panic if the length of `dst` is not enough. #[inline] #[must_use] pub fn encode_as_str<'d>(src: &[u8], dst: Out<'d, [u8]>, case: AsciiCase) -> &'d mut str { let ans = encode(src, dst, case); unsafe { core::str::from_utf8_unchecked_mut(ans) } } /// Types that can be decoded from a hex string. pub trait FromHexDecode: Sized { /// Decodes a hex string to bytes case-insensitively and returns the self type. /// /// # Errors /// This function returns `Err` if the content of `data` is invalid. fn from_hex_decode(data: &[u8]) -> Result; } /// Types that can represent a hex string. pub trait FromHexEncode: Sized { /// Encodes bytes to a hex string and returns the self type. fn from_hex_encode(data: &[u8], case: AsciiCase) -> Self; } /// Encodes bytes to a hex string and returns a specified type. #[inline] #[must_use] pub fn encode_type(data: impl AsRef<[u8]>, case: AsciiCase) -> T { T::from_hex_encode(data.as_ref(), case) } /// Decodes a hex string to bytes case-insensitively and returns a specified type. /// /// # Errors /// This function returns `Err` if the content of `data` is invalid. #[inline] pub fn decode_type(data: impl AsRef<[u8]>) -> Result { T::from_hex_decode(data.as_ref()) } /// Types that can append a hex string. pub trait AppendHexEncode: FromHexEncode { /// Encodes bytes to a hex string and appends to the self type. fn append_hex_encode(src: &[u8], dst: &mut Self, case: AsciiCase); } /// Types that can append bytes decoded from a hex string. pub trait AppendHexDecode: FromHexDecode { /// Decodes a hex string to bytes case-insensitively and appends to the self type. /// /// # Errors /// This function returns `Err` if the content of `src` is invalid. fn append_hex_decode(src: &[u8], dst: &mut Self) -> Result<(), Error>; } /// Encodes bytes to a hex string and appends to a specified type. #[inline] pub fn encode_append(src: impl AsRef<[u8]>, dst: &mut T, case: AsciiCase) { T::append_hex_encode(src.as_ref(), dst, case); } /// Decodes a hex string to bytes case-insensitively and appends to a specified type. /// /// # Errors /// This function returns `Err` if the content of `src` is invalid. #[inline] pub fn decode_append(src: impl AsRef<[u8]>, dst: &mut T) -> Result<(), Error> { T::append_hex_decode(src.as_ref(), dst) } /// Encodes bytes to a hex string. #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] #[cfg(feature = "alloc")] #[inline] #[must_use] pub fn encode_to_string(data: impl AsRef<[u8]>, case: AsciiCase) -> String { encode_type(data, case) } /// Decodes a hex string to bytes case-insensitively. /// /// # 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(data: impl AsRef<[u8]>) -> Result, Error> { decode_type(data) } hex-simd-0.8.0/src/multiversion.rs000064400000000000000000000021241046102023000152430ustar 00000000000000#![allow(missing_docs)] use crate::{AsciiCase, Error}; vsimd::dispatch!( name = {check}, signature = {pub unsafe fn(src: *const u8, len: usize) -> Result<(), Error>}, fallback = {crate::check::check_fallback}, simd = {crate::check::check_simd}, targets = {"avx2", "sse2", "neon", "simd128"}, fastest = {"avx2", "neon", "simd128"}, ); vsimd::dispatch!( name = {encode}, signature = {pub unsafe fn(src: *const u8, len: usize, dst: *mut u8, case: AsciiCase) -> () }, fallback = {crate::encode::encode_fallback}, simd = {crate::encode::encode_simd}, targets = {"avx2", "ssse3", "sse2", "neon", "simd128"}, fastest = {"avx2", "neon", "simd128"}, ); vsimd::dispatch!( name = {decode}, signature = {pub unsafe fn(src: *const u8, len: usize, dst: *mut u8) -> Result<(), Error>}, fallback = {crate::decode::decode_fallback}, simd = {crate::decode::decode_simd}, targets = {"avx2", "ssse3", "sse2", "neon", "simd128"}, fastest = {"avx2", "neon", "simd128"}, ); hex-simd-0.8.0/tests/it.rs000064400000000000000000000115121046102023000134730ustar 00000000000000use hex_simd::{AsOut, AsciiCase}; use core::mem::MaybeUninit; 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 as_str() { let src = "hello"; let mut buf = [MaybeUninit::::uninit(); 10]; let ans = hex_simd::encode_as_str(src.as_bytes(), buf.as_mut_slice().as_out(), AsciiCase::Lower); assert_eq!(ans, "68656c6c6f"); } #[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 = "hello"; let ans: String = hex_simd::encode_type(src, AsciiCase::Lower); assert_eq!(&*ans, "68656c6c6f"); let ans: Vec = hex_simd::decode_type(ans).unwrap(); assert_eq!(&*ans, src.as_bytes()); } { let src = [1, 2, 3]; let prefix = "0x"; let mut encode_buf = prefix.to_owned(); hex_simd::encode_append(src, &mut encode_buf, AsciiCase::Lower); assert_eq!(encode_buf, format!("{prefix}010203")); let mut decode_buf = b"123".to_vec(); let src = &encode_buf[prefix.len()..]; hex_simd::decode_append(src, &mut decode_buf).unwrap(); assert_eq!(decode_buf, b"123\x01\x02\x03"); } } #[cfg_attr(not(target_arch = "wasm32"), test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn random() { let ok_cases: Vec> = { let mut ans = Vec::new(); for n in 0..128usize { dbgmsg!("generating ok case n = {}", n); let iter = (0..16).cycle().take(n).map(|x| char::from_digit(x, 16).unwrap() as u8); ans.push(iter.collect()); } ans }; let err_cases: Vec> = { vec![ vec![0], vec![b'0', 0], vec![b'a', b'f', 0], vec![b'a', b'0', b'c', 0], vec![b'a', b'0', b'c', b'1', 0], ] }; macro_rules! test_decode_encode { ($src: expr, $case: expr) => {{ let mut decode_buf = vec![0; $src.len() / 2]; let mut encode_buf = vec![0; $src.len()]; let decode_buf = hex_simd::decode($src, decode_buf.as_out()).unwrap(); let encode_buf = hex_simd::encode(decode_buf, encode_buf.as_out(), $case); assert_eq!(encode_buf, $src); }}; } macro_rules! test_decode_inplace_encode { ($src: expr, $case: expr) => {{ let mut decode_buf = $src.to_owned(); let mut encode_buf = vec![0; $src.len()]; let decode_buf = hex_simd::decode_inplace(&mut decode_buf).unwrap(); let encode_buf = hex_simd::encode(decode_buf, encode_buf.as_out(), $case); assert_eq!(encode_buf, $src); }}; } macro_rules! test_encode_decode { ($src: expr, $case: expr) => {{ let mut encode_buf = vec![0; $src.len() * 2]; let mut decode_buf = vec![0; $src.len()]; let encode_buf = hex_simd::encode($src, encode_buf.as_out(), $case); let decode_buf = hex_simd::decode(encode_buf, decode_buf.as_out()).unwrap(); assert_eq!(decode_buf, $src); }}; } macro_rules! test_encode_decode_inplace { ($src: expr, $case: expr) => {{ let mut encode_buf = vec![0; $src.len() * 2]; let encode_buf = hex_simd::encode($src, encode_buf.as_out(), $case); let decode_buf = hex_simd::decode_inplace(encode_buf).unwrap(); assert_eq!(decode_buf, $src); }}; } for (_, src) in ok_cases.iter().enumerate() { // dbgmsg!("ok case {}", i + 1); assert!(hex_simd::check(src).is_ok()); if src.len() % 2 == 0 { test_decode_encode!(src, AsciiCase::Lower); test_decode_inplace_encode!(src, AsciiCase::Lower); } else { test_encode_decode!(src, AsciiCase::Upper); test_encode_decode_inplace!(src, AsciiCase::Lower); } } for (_, src) in err_cases.iter().enumerate() { // dbgmsg!("err case {}", i + 1); assert!(hex_simd::check(src).is_err()); let mut buf = vec![0; src.len() / 2]; assert!(hex_simd::decode(src, buf.as_out()).is_err(), "src = {src:?}"); } for n in 0..128 { dbgmsg!("rand case n = {}", n); let bytes = rand_bytes(n); let src = bytes.as_slice(); test_encode_decode!(src, AsciiCase::Lower); test_encode_decode_inplace!(src, AsciiCase::Upper); } }