#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct SemVer {
pub major: i32,
pub minor: i32,
pub patch: i32,
}
#[repr(C)]
#[derive(Debug, PartialEq)]
pub enum Status {
Success = 0,
BadAlloc = -10,
InvalidUtf8 = -12,
ContainsDuplicates = -13,
OverflowRisk = -14,
UnexpectedDimensions = -15,
MissingGpu = -16,
DeviceCodeMismatch = -17,
DeviceMemoryMismatch = -18,
AuthenticationFailed = -19,
StatusUnknown = -1,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Utf8NormalForm {
Nfd = 0,
Nfc = 1,
Nfkd = 2,
Nfkc = 3,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct Byteset {
bits: [u64; 4],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct IndexSpan {
pub offset: usize,
pub length: usize,
}
impl IndexSpan {
#[inline]
pub const fn new(offset: usize, length: usize) -> Self {
Self { offset, length }
}
#[inline]
pub const fn range(&self) -> core::ops::Range<usize> {
self.offset..self.offset + self.length
}
#[inline]
pub fn extract<'a>(&self, text: &'a [u8]) -> &'a [u8] {
&text[self.range()]
}
#[inline]
pub const fn end(&self) -> usize {
self.offset + self.length
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub(crate) struct Utf8UncasedNeedleMetadata {
offset_in_unfolded: usize,
length_in_unfolded: usize,
folded_slice: [u8; 16],
folded_slice_length: u8,
probe_second: u8,
probe_third: u8,
kernel_id: u8,
}
impl Utf8UncasedNeedleMetadata {
pub(crate) const UNANALYZED: Self = Self {
offset_in_unfolded: 0,
length_in_unfolded: 0,
folded_slice: [0; 16],
folded_slice_length: 0,
probe_second: 0,
probe_third: 0,
kernel_id: 0, };
}
impl Default for Utf8UncasedNeedleMetadata {
fn default() -> Self {
Self::UNANALYZED
}
}
pub struct Utf8UncasedNeedle<'a> {
needle: &'a [u8],
metadata: UnsafeCell<Utf8UncasedNeedleMetadata>,
}
impl<'a> Utf8UncasedNeedle<'a> {
#[inline]
pub const fn new(needle: &'a [u8]) -> Self {
Self {
needle,
metadata: UnsafeCell::new(Utf8UncasedNeedleMetadata::UNANALYZED),
}
}
#[inline]
pub const fn as_bytes(&self) -> &[u8] {
self.needle
}
#[inline]
pub const fn len(&self) -> usize {
self.needle.len()
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.needle.is_empty()
}
#[inline]
pub(crate) fn metadata_ptr(&self) -> *mut Utf8UncasedNeedleMetadata {
self.metadata.get()
}
}
unsafe impl<'a> Send for Utf8UncasedNeedle<'a> {}
unsafe impl<'a> Sync for Utf8UncasedNeedle<'a> {}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
#[repr(align(64))] pub struct Hasher {
aes: [u64; 8],
sum: [u64; 8],
ins: [u64; 8], key: [u64; 2],
ins_length: usize, }
pub const SHA256_DIGEST_LENGTH: usize = 32;
pub const SHA256_BLOCK_LENGTH: usize = 64;
pub type Sha256Digest = [u8; SHA256_DIGEST_LENGTH];
#[repr(C, align(8))]
#[derive(Debug, Clone, Copy)]
pub struct Sha256([u8; 128]);
const _: () = assert!(core::mem::size_of::<Sha256>() == 128);
const _: () = assert!(core::mem::align_of::<Sha256>() == 8);
pub const AES256_KEY_LENGTH: usize = 32;
pub const AES256_NONCE_LENGTH: usize = 12;
pub const AES256_TAG_LENGTH: usize = 16;
const AES256_ROUND_KEYS: usize = 60;
const AES256_GALOIS_POWERS: usize = 8 * 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthenticationError {
TagMismatch,
UnexpectedStatus(i32),
}
#[repr(C)]
pub struct Aes256CtrKey {
round_keys: [u32; AES256_ROUND_KEYS],
}
#[repr(C)]
pub struct Aes256GcmKey {
block: Aes256CtrKey,
powers: [u8; AES256_GALOIS_POWERS],
}
#[repr(C)]
struct Aes256GcmState {
key: Aes256GcmKey,
accumulator: [u8; 16],
counter: [u8; 16],
tag_mask: [u8; 16],
partial: [u8; 16],
keystream: [u8; 16],
associated_length: u64,
text_length: u64,
buffered: u8,
keystream_used: u8,
}
#[repr(C)]
pub struct Aes256GcmEncryptor {
state: Aes256GcmState,
}
#[repr(C)]
pub struct Aes256GcmDecryptor {
state: Aes256GcmState,
}
pub type SortedIdx = usize;
pub trait SequenceData {
type Item;
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn index(&self, idx: usize) -> &Self::Item;
}
impl<Element> SequenceData for [Element] {
type Item = Element;
#[inline]
fn len(&self) -> usize {
self.len()
}
#[inline]
fn index(&self, idx: usize) -> &Element {
&self[idx]
}
}
#[repr(C)]
pub struct _SzSequence {
pub handle: *const c_void,
pub count: usize,
pub get_start: Option<unsafe extern "C" fn(handle: *const c_void, idx: usize) -> *const c_void>,
pub get_length: Option<unsafe extern "C" fn(handle: *const c_void, idx: usize) -> usize>,
}
impl Byteset {
#[inline]
pub const fn new() -> Self {
Self { bits: [0; 4] }
}
#[inline]
pub const fn new_ascii() -> Self {
Self {
bits: [u64::MAX, u64::MAX, 0, 0],
}
}
#[inline]
pub const fn add_u8(&mut self, byte: u8) {
let word = (byte >> 6) as usize; let bit = byte & 63; self.bits[word] |= 1 << bit;
}
#[inline]
pub const fn add(&mut self, character: char) {
self.add_u8(character as u8);
}
#[inline]
pub const fn invert(&mut self) {
let mut word = 0;
while word < 4 {
self.bits[word] = !self.bits[word];
word += 1;
}
}
#[inline]
pub const fn inverted(&self) -> Self {
Self {
bits: [!self.bits[0], !self.bits[1], !self.bits[2], !self.bits[3]],
}
}
#[inline]
pub const fn from_bytes(bytes: &[u8]) -> Self {
let mut set = Self::new();
let mut index = 0;
while index < bytes.len() {
set.add_u8(bytes[index]);
index += 1;
}
set
}
}
impl Default for Byteset {
fn default() -> Self {
Self::new()
}
}
impl<Source: AsRef<[u8]>> From<Source> for Byteset {
#[inline]
fn from(bytes: Source) -> Self {
Self::from_bytes(bytes.as_ref())
}
}
use core::cell::UnsafeCell;
use core::cmp::Ordering;
use core::ffi::{c_char, c_void, CStr};
use core::fmt::{self, Write};
extern "C" {
pub(crate) fn sz_dynamic_dispatch() -> i32;
pub(crate) fn sz_version_major() -> i32;
pub(crate) fn sz_version_minor() -> i32;
pub(crate) fn sz_version_patch() -> i32;
pub(crate) fn sz_capabilities() -> u32;
pub(crate) fn sz_capabilities_to_string(caps: u32) -> *const c_void;
pub(crate) fn sz_copy(target: *const c_void, source: *const c_void, length: usize);
pub(crate) fn sz_fill(target: *const c_void, length: usize, value: u8);
pub(crate) fn sz_move(target: *const c_void, source: *const c_void, length: usize);
pub(crate) fn sz_fill_random(text: *mut c_void, length: usize, seed: u64);
pub(crate) fn sz_lookup(target: *const c_void, length: usize, source: *const c_void, lut: *const u8);
pub(crate) fn sz_find(
haystack: *const c_void,
haystack_length: usize,
needle: *const c_void,
needle_length: usize,
) -> *const c_void;
pub(crate) fn sz_rfind(
haystack: *const c_void,
haystack_length: usize,
needle: *const c_void,
needle_length: usize,
) -> *const c_void;
pub(crate) fn sz_find_byteset(
haystack: *const c_void,
haystack_length: usize,
byteset: *const c_void,
) -> *const c_void;
pub(crate) fn sz_rfind_byteset(
haystack: *const c_void,
haystack_length: usize,
byteset: *const c_void,
) -> *const c_void;
pub(crate) fn sz_utf8_count(text: *const c_void, length: usize) -> usize;
pub(crate) fn sz_utf8_seek(text: *const c_void, length: usize, n: usize) -> *const c_void;
pub(crate) fn sz_utf8_decode(
text: *const c_void,
length: usize,
runes: *mut u32,
runes_capacity: usize,
runes_unpacked: *mut usize,
) -> *const c_void;
pub(crate) fn sz_utf8_newlines(
text: *const c_void,
length: usize,
match_offsets: *mut usize,
match_lengths: *mut usize,
matches_capacity: usize,
bytes_consumed: *mut usize,
) -> usize;
pub(crate) fn sz_utf8_whitespaces(
text: *const c_void,
length: usize,
match_offsets: *mut usize,
match_lengths: *mut usize,
matches_capacity: usize,
bytes_consumed: *mut usize,
) -> usize;
pub(crate) fn sz_utf8_delimiters(
text: *const c_void,
length: usize,
match_offsets: *mut usize,
match_lengths: *mut usize,
matches_capacity: usize,
bytes_consumed: *mut usize,
) -> usize;
pub(crate) fn sz_utf8_uncased_fold(source: *const c_void, source_length: usize, destination: *mut c_void) -> usize;
pub(crate) fn sz_utf8_norm(
source: *const c_void,
source_length: usize,
form: i32,
destination: *mut c_void,
) -> usize;
pub(crate) fn sz_utf8_find_denormalized(source: *const c_void, source_length: usize, form: i32) -> *const c_void;
pub(crate) fn sz_utf8_uncased_search(
haystack: *const c_void,
haystack_length: usize,
needle: *const c_void,
needle_length: usize,
needle_metadata: *mut Utf8UncasedNeedleMetadata,
matched_length: *mut usize,
) -> *const c_void;
pub(crate) fn sz_utf8_uncased_order(a: *const c_void, a_length: usize, b: *const c_void, b_length: usize) -> i32;
pub(crate) fn sz_utf8_wordbreaks(
text: *const c_void,
length: usize,
word_starts: *mut usize,
word_lengths: *mut usize,
words_capacity: usize,
bytes_consumed: *mut usize,
) -> usize;
pub(crate) fn sz_utf8_graphemes(
text: *const c_void,
length: usize,
starts: *mut usize,
lengths: *mut usize,
cap: usize,
consumed: *mut usize,
) -> usize;
pub(crate) fn sz_utf8_sentences(
text: *const c_void,
length: usize,
starts: *mut usize,
lengths: *mut usize,
cap: usize,
consumed: *mut usize,
) -> usize;
pub(crate) fn sz_utf8_linebreaks(
text: *const c_void,
length: usize,
starts: *mut usize,
lengths: *mut usize,
cap: usize,
consumed: *mut usize,
) -> usize;
pub(crate) fn sz_equal(a: *const c_void, b: *const c_void, length: usize) -> i32;
pub(crate) fn sz_order(a: *const c_void, a_length: usize, b: *const c_void, b_length: usize) -> i32;
pub(crate) fn sz_bytesum(text: *const c_void, length: usize) -> u64;
pub(crate) fn sz_hash(text: *const c_void, length: usize, seed: u64) -> u64;
pub(crate) fn sz_hash_multiseed(
text: *const c_void,
length: usize,
seeds: *const u64,
seeds_count: usize,
hashes: *mut u64,
);
pub(crate) fn sz_hash_state_init(state: *const c_void, seed: u64);
pub(crate) fn sz_hash_state_update(state: *const c_void, text: *const c_void, length: usize);
pub(crate) fn sz_hash_state_digest(state: *const c_void) -> u64;
pub(crate) fn sz_sha256_state_init(state: *const c_void);
pub(crate) fn sz_sha256_state_update(state: *const c_void, data: *const c_void, length: usize);
pub(crate) fn sz_sha256_state_digest(state: *const c_void, digest: *mut u8);
pub(crate) fn sz_sha256_multistate_update(states: *mut c_void, texts: *const _SzSequence);
pub(crate) fn sz_sha256_multistate_digest(states: *const c_void, states_count: usize, digests: *mut u8);
pub(crate) fn sz_aes256_key_init(key: *mut c_void, secret: *const u8);
pub(crate) fn sz_aes256_ctr_xor(
key: *const c_void,
nonce: *const u8,
byte_offset: u64,
text: *const c_void,
length: usize,
output: *mut c_void,
);
pub(crate) fn sz_aes256_gcm_key_init(key: *mut c_void, secret: *const u8);
pub(crate) fn sz_aes256_gcm_encrypt(
key: *const c_void,
nonce: *const u8,
associated: *const c_void,
associated_length: usize,
text: *const c_void,
length: usize,
output: *mut c_void,
tag: *mut u8,
);
pub(crate) fn sz_aes256_gcm_decrypt(
key: *const c_void,
nonce: *const u8,
associated: *const c_void,
associated_length: usize,
text: *const c_void,
length: usize,
output: *mut c_void,
tag: *const u8,
) -> i32;
pub(crate) fn sz_aes256_gcm_encryptor_init(encryptor: *mut c_void, key: *const c_void, nonce: *const u8);
pub(crate) fn sz_aes256_gcm_encryptor_associate(encryptor: *mut c_void, text: *const c_void, length: usize);
pub(crate) fn sz_aes256_gcm_encryptor_update(
encryptor: *mut c_void,
text: *const c_void,
length: usize,
output: *mut c_void,
);
pub(crate) fn sz_aes256_gcm_encryptor_digest(encryptor: *const c_void, tag: *mut u8);
pub(crate) fn sz_aes256_gcm_decryptor_init(decryptor: *mut c_void, key: *const c_void, nonce: *const u8);
pub(crate) fn sz_aes256_gcm_decryptor_associate(decryptor: *mut c_void, text: *const c_void, length: usize);
pub(crate) fn sz_aes256_gcm_decryptor_update_unverified(
decryptor: *mut c_void,
text: *const c_void,
length: usize,
output: *mut c_void,
);
pub(crate) fn sz_aes256_gcm_decryptor_verify(decryptor: *const c_void, tag: *const u8) -> i32;
pub(crate) fn sz_sequence_argsort(
sequence: *const _SzSequence,
alloc: *const c_void,
order: *mut SortedIdx,
top_count: usize,
reverse: i32,
) -> Status;
pub(crate) fn sz_sequence_argsort_uncased(
sequence: *const _SzSequence,
alloc: *const c_void,
order: *mut SortedIdx,
top_count: usize,
reverse: i32,
) -> Status;
pub(crate) fn sz_sequence_intersect(
first_sequence: *const _SzSequence,
second_sequence: *const _SzSequence,
alloc: *const c_void,
seed: u64,
intersection_size: *mut usize,
first_positions: *mut SortedIdx,
second_positions: *mut SortedIdx,
) -> Status;
}
impl SemVer {
pub const fn new(major: i32, minor: i32, patch: i32) -> Self {
Self { major, minor, patch }
}
}
impl Hasher {
pub fn new(seed: u64) -> Self {
let mut state = Hasher {
aes: [0; 8],
sum: [0; 8],
ins: [0; 8],
key: [0; 2],
ins_length: 0,
};
unsafe {
sz_hash_state_init(&mut state as *mut _ as *mut c_void, seed);
}
state
}
pub fn update(&mut self, data: &[u8]) -> &mut Self {
unsafe {
sz_hash_state_update(
self as *mut _ as *mut c_void,
data.as_ptr() as *const c_void,
data.len(),
);
}
self
}
pub fn digest(&self) -> u64 {
unsafe { sz_hash_state_digest(self as *const _ as *const c_void) }
}
}
impl PartialEq for Hasher {
fn eq(&self, other: &Self) -> bool {
self.aes == other.aes && self.sum == other.sum && self.key == other.key
}
}
impl Default for Hasher {
#[inline]
fn default() -> Self {
Hasher::new(0)
}
}
impl Sha256 {
pub fn new() -> Self {
let mut state = Sha256([0; 128]);
unsafe {
sz_sha256_state_init(&mut state as *mut _ as *mut c_void);
}
state
}
pub fn update(&mut self, data: &[u8]) -> &mut Self {
unsafe {
sz_sha256_state_update(
self as *mut _ as *mut c_void,
data.as_ptr() as *const c_void,
data.len(),
);
}
self
}
pub fn digest(&self) -> Sha256Digest {
let mut digest = [0u8; SHA256_DIGEST_LENGTH];
unsafe {
sz_sha256_state_digest(self as *const _ as *const c_void, digest.as_mut_ptr());
}
digest
}
pub fn hash(data: &[u8]) -> Sha256Digest {
let mut hasher = Sha256::new();
hasher.update(data);
hasher.digest()
}
}
impl Default for Sha256 {
#[inline]
fn default() -> Self {
Sha256::new()
}
}
pub fn sha256_multistate_update<Element: AsRef<[u8]>>(states: &mut [Sha256], chunks: &[Element]) -> Result<(), Status> {
if chunks.len() != states.len() {
return Err(Status::BadAlloc);
}
sha256_multistate_update_by(states, |lane_index| chunks[lane_index].as_ref())
}
pub fn sha256_multistate_update_by<Mapper, Key>(states: &mut [Sha256], mapper: Mapper) -> Result<(), Status>
where
Mapper: Fn(usize) -> Key,
Key: AsRef<[u8]>,
{
if states.is_empty() {
return Ok(());
}
let adapter = move |lane_index: usize| -> &'static [u8] {
let binding = mapper(lane_index);
let slice = binding.as_ref();
unsafe { core::mem::transmute(slice) }
};
_sha256_multistate_update_impl(adapter, states)
}
fn _sha256_multistate_update_impl<Adapter>(adapter: Adapter, states: &mut [Sha256]) -> Result<(), Status>
where
Adapter: Fn(usize) -> &'static [u8],
{
let wrapper = _PunnedSliceLookupView {
get_slice: unsafe { _get_slice_fn::<Adapter>() },
data: &adapter as *const Adapter as *const c_void,
};
let texts = _SzSequence {
handle: &wrapper as *const _ as *const c_void,
count: states.len(),
get_start: Some(_slice_get_start_punned),
get_length: Some(_slice_get_length_punned),
};
unsafe { sz_sha256_multistate_update(states.as_mut_ptr() as *mut c_void, &texts) };
Ok(())
}
pub fn sha256_multistate_digest(states: &[Sha256], digests: &mut [Sha256Digest]) -> Result<(), Status> {
if digests.len() != states.len() {
return Err(Status::BadAlloc);
}
if states.is_empty() {
return Ok(());
}
unsafe {
sz_sha256_multistate_digest(
states.as_ptr() as *const c_void,
states.len(),
digests.as_mut_ptr() as *mut u8,
)
};
Ok(())
}
pub fn hmac_sha256(key: &[u8], message: &[u8]) -> Sha256Digest {
let (mut inner, outer) = _hmac_sha256_primed(key);
inner.update(message);
_hmac_sha256_wrap(&inner, &outer)
}
fn _hmac_sha256_primed(key: &[u8]) -> (Sha256, Sha256) {
let mut key_pad = [0u8; SHA256_BLOCK_LENGTH];
if key.len() > SHA256_BLOCK_LENGTH {
key_pad[..SHA256_DIGEST_LENGTH].copy_from_slice(&Sha256::hash(key));
} else {
key_pad[..key.len()].copy_from_slice(key);
}
let mut block = [0u8; SHA256_BLOCK_LENGTH];
let mut inner = Sha256::new();
for byte_index in 0..SHA256_BLOCK_LENGTH {
block[byte_index] = key_pad[byte_index] ^ 0x36;
}
inner.update(&block);
let mut outer = Sha256::new();
for byte_index in 0..SHA256_BLOCK_LENGTH {
block[byte_index] = key_pad[byte_index] ^ 0x5c;
}
outer.update(&block);
unsafe {
core::ptr::write_volatile(&mut key_pad, [0u8; SHA256_BLOCK_LENGTH]);
core::ptr::write_volatile(&mut block, [0u8; SHA256_BLOCK_LENGTH]);
}
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
(inner, outer)
}
fn _hmac_sha256_wrap(inner: &Sha256, outer: &Sha256) -> Sha256Digest {
let mut wrapping = *outer;
wrapping.update(&inner.digest());
wrapping.digest()
}
pub fn hmac_sha256_multistate<Element: AsRef<[u8]>>(
key: &[u8],
messages: &[Element],
states: &mut [Sha256],
tags: &mut [Sha256Digest],
) -> Result<(), Status> {
if messages.len() != states.len() || messages.len() != tags.len() {
return Err(Status::BadAlloc);
}
if messages.is_empty() {
return Ok(());
}
let (inner, outer) = _hmac_sha256_primed(key);
for state in states.iter_mut() {
*state = inner;
}
sha256_multistate_update(states, messages)?;
sha256_multistate_digest(states, tags)?;
for state in states.iter_mut() {
*state = outer;
}
{
let inner_digests = &*tags;
sha256_multistate_update_by(states, |lane_index| &inner_digests[lane_index][..])?;
}
sha256_multistate_digest(states, tags)
}
impl core::hash::Hasher for Hasher {
#[inline]
fn finish(&self) -> u64 {
self.digest()
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
let _ = self.update(bytes);
}
#[inline]
fn write_u8(&mut self, i: u8) {
self.write(&[i]);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write(&i.to_le_bytes());
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write(&i.to_le_bytes());
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.write(&i.to_le_bytes());
}
#[inline]
fn write_u128(&mut self, i: u128) {
self.write(&i.to_le_bytes());
}
#[inline]
fn write_usize(&mut self, i: usize) {
self.write(&i.to_le_bytes());
}
#[inline]
fn write_i8(&mut self, i: i8) {
self.write(&i.to_le_bytes());
}
#[inline]
fn write_i16(&mut self, i: i16) {
self.write(&i.to_le_bytes());
}
#[inline]
fn write_i32(&mut self, i: i32) {
self.write(&i.to_le_bytes());
}
#[inline]
fn write_i64(&mut self, i: i64) {
self.write(&i.to_le_bytes());
}
#[inline]
fn write_i128(&mut self, i: i128) {
self.write(&i.to_le_bytes());
}
#[inline]
fn write_isize(&mut self, i: isize) {
self.write(&i.to_le_bytes());
}
}
#[cfg(feature = "std")]
#[derive(Debug, Clone, Copy, Default)]
pub struct BuildSzHasher {
pub seed: u64,
}
#[cfg(feature = "std")]
impl BuildSzHasher {
#[inline]
pub const fn with_seed(seed: u64) -> Self {
Self { seed }
}
}
#[cfg(feature = "std")]
impl std::hash::BuildHasher for BuildSzHasher {
type Hasher = Hasher;
#[inline]
fn build_hasher(&self) -> Self::Hasher {
Hasher::new(self.seed)
}
}
fn authentication_result_from_status(status: i32) -> Result<(), AuthenticationError> {
const SUCCESS: i32 = Status::Success as i32;
const AUTHENTICATION_FAILED: i32 = Status::AuthenticationFailed as i32;
match status {
SUCCESS => Ok(()),
AUTHENTICATION_FAILED => Err(AuthenticationError::TagMismatch),
other => Err(AuthenticationError::UnexpectedStatus(other)),
}
}
impl Aes256CtrKey {
pub fn new(secret: &[u8; AES256_KEY_LENGTH]) -> Self {
let mut key = Aes256CtrKey::zeroed();
unsafe { sz_aes256_key_init(&mut key as *mut _ as *mut c_void, secret.as_ptr()) };
key
}
pub fn xor_into(&self, nonce: &[u8; AES256_NONCE_LENGTH], byte_offset: u64, text: &[u8], output: &mut [u8]) {
assert_eq!(text.len(), output.len(), "`output` must be as long as `text`");
unsafe {
sz_aes256_ctr_xor(
self as *const _ as *const c_void,
nonce.as_ptr(),
byte_offset,
text.as_ptr() as *const c_void,
text.len(),
output.as_mut_ptr() as *mut c_void,
)
}
}
pub fn xor_in_place(&self, nonce: &[u8; AES256_NONCE_LENGTH], byte_offset: u64, text: &mut [u8]) {
let length = text.len();
let pointer = text.as_mut_ptr() as *mut c_void;
unsafe {
sz_aes256_ctr_xor(
self as *const _ as *const c_void,
nonce.as_ptr(),
byte_offset,
pointer as *const c_void,
length,
pointer,
)
}
}
const fn zeroed() -> Self {
Aes256CtrKey {
round_keys: [0; AES256_ROUND_KEYS],
}
}
}
impl Drop for Aes256CtrKey {
fn drop(&mut self) {
unsafe { core::ptr::write_volatile(self as *mut Self, Self::zeroed()) };
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
}
impl fmt::Debug for Aes256CtrKey {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("Aes256CtrKey(<secret>)")
}
}
impl Aes256GcmKey {
pub fn new(secret: &[u8; AES256_KEY_LENGTH]) -> Self {
let mut key = Aes256GcmKey::zeroed();
unsafe { sz_aes256_gcm_key_init(&mut key as *mut _ as *mut c_void, secret.as_ptr()) };
key
}
pub fn encrypt_into(
&self,
nonce: &[u8; AES256_NONCE_LENGTH],
associated: &[u8],
text: &[u8],
output: &mut [u8],
) -> [u8; AES256_TAG_LENGTH] {
assert_eq!(text.len(), output.len(), "`output` must be as long as `text`");
let mut tag = [0u8; AES256_TAG_LENGTH];
unsafe {
sz_aes256_gcm_encrypt(
self as *const _ as *const c_void,
nonce.as_ptr(),
associated.as_ptr() as *const c_void,
associated.len(),
text.as_ptr() as *const c_void,
text.len(),
output.as_mut_ptr() as *mut c_void,
tag.as_mut_ptr(),
)
};
tag
}
pub fn encrypt_in_place(
&self,
nonce: &[u8; AES256_NONCE_LENGTH],
associated: &[u8],
text: &mut [u8],
) -> [u8; AES256_TAG_LENGTH] {
let mut tag = [0u8; AES256_TAG_LENGTH];
let length = text.len();
let pointer = text.as_mut_ptr() as *mut c_void;
unsafe {
sz_aes256_gcm_encrypt(
self as *const _ as *const c_void,
nonce.as_ptr(),
associated.as_ptr() as *const c_void,
associated.len(),
pointer as *const c_void,
length,
pointer,
tag.as_mut_ptr(),
)
};
tag
}
pub fn decrypt_into(
&self,
nonce: &[u8; AES256_NONCE_LENGTH],
associated: &[u8],
text: &[u8],
output: &mut [u8],
tag: &[u8; AES256_TAG_LENGTH],
) -> Result<(), AuthenticationError> {
assert_eq!(text.len(), output.len(), "`output` must be as long as `text`");
let status = unsafe {
sz_aes256_gcm_decrypt(
self as *const _ as *const c_void,
nonce.as_ptr(),
associated.as_ptr() as *const c_void,
associated.len(),
text.as_ptr() as *const c_void,
text.len(),
output.as_mut_ptr() as *mut c_void,
tag.as_ptr(),
)
};
authentication_result_from_status(status)
}
pub fn decrypt_in_place(
&self,
nonce: &[u8; AES256_NONCE_LENGTH],
associated: &[u8],
text: &mut [u8],
tag: &[u8; AES256_TAG_LENGTH],
) -> Result<(), AuthenticationError> {
let length = text.len();
let pointer = text.as_mut_ptr() as *mut c_void;
let status = unsafe {
sz_aes256_gcm_decrypt(
self as *const _ as *const c_void,
nonce.as_ptr(),
associated.as_ptr() as *const c_void,
associated.len(),
pointer as *const c_void,
length,
pointer,
tag.as_ptr(),
)
};
authentication_result_from_status(status)
}
const fn zeroed() -> Self {
Aes256GcmKey {
block: Aes256CtrKey::zeroed(),
powers: [0; AES256_GALOIS_POWERS],
}
}
}
impl Drop for Aes256GcmKey {
fn drop(&mut self) {
unsafe { core::ptr::write_volatile(self as *mut Self, Self::zeroed()) };
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
}
impl fmt::Debug for Aes256GcmKey {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("Aes256GcmKey(<secret>)")
}
}
impl Aes256GcmState {
const fn zeroed() -> Self {
Aes256GcmState {
key: Aes256GcmKey::zeroed(),
accumulator: [0; 16],
counter: [0; 16],
tag_mask: [0; 16],
partial: [0; 16],
keystream: [0; 16],
associated_length: 0,
text_length: 0,
buffered: 0,
keystream_used: 0,
}
}
}
impl Drop for Aes256GcmEncryptor {
fn drop(&mut self) {
unsafe { core::ptr::write_volatile(&mut self.state as *mut Aes256GcmState, Aes256GcmState::zeroed()) };
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
}
impl Drop for Aes256GcmDecryptor {
fn drop(&mut self) {
unsafe { core::ptr::write_volatile(&mut self.state as *mut Aes256GcmState, Aes256GcmState::zeroed()) };
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
}
impl Aes256GcmEncryptor {
pub fn new(key: &Aes256GcmKey, nonce: &[u8; AES256_NONCE_LENGTH]) -> Self {
let mut encryptor = Aes256GcmEncryptor {
state: Aes256GcmState::zeroed(),
};
unsafe {
sz_aes256_gcm_encryptor_init(
&mut encryptor as *mut _ as *mut c_void,
key as *const _ as *const c_void,
nonce.as_ptr(),
)
};
encryptor
}
pub fn associate(&mut self, associated: &[u8]) -> &mut Self {
unsafe {
sz_aes256_gcm_encryptor_associate(
self as *mut _ as *mut c_void,
associated.as_ptr() as *const c_void,
associated.len(),
)
};
self
}
pub fn encrypt_into(&mut self, text: &[u8], output: &mut [u8]) -> &mut Self {
assert_eq!(text.len(), output.len(), "`output` must be as long as `text`");
unsafe {
sz_aes256_gcm_encryptor_update(
self as *mut _ as *mut c_void,
text.as_ptr() as *const c_void,
text.len(),
output.as_mut_ptr() as *mut c_void,
)
};
self
}
pub fn encrypt_in_place(&mut self, text: &mut [u8]) -> &mut Self {
let length = text.len();
let pointer = text.as_mut_ptr() as *mut c_void;
unsafe {
sz_aes256_gcm_encryptor_update(self as *mut _ as *mut c_void, pointer as *const c_void, length, pointer)
};
self
}
pub fn digest(&self) -> [u8; AES256_TAG_LENGTH] {
let mut tag = [0u8; AES256_TAG_LENGTH];
unsafe { sz_aes256_gcm_encryptor_digest(self as *const _ as *const c_void, tag.as_mut_ptr()) };
tag
}
}
impl Aes256GcmDecryptor {
pub fn new(key: &Aes256GcmKey, nonce: &[u8; AES256_NONCE_LENGTH]) -> Self {
let mut decryptor = Aes256GcmDecryptor {
state: Aes256GcmState::zeroed(),
};
unsafe {
sz_aes256_gcm_decryptor_init(
&mut decryptor as *mut _ as *mut c_void,
key as *const _ as *const c_void,
nonce.as_ptr(),
)
};
decryptor
}
pub fn associate(&mut self, associated: &[u8]) -> &mut Self {
unsafe {
sz_aes256_gcm_decryptor_associate(
self as *mut _ as *mut c_void,
associated.as_ptr() as *const c_void,
associated.len(),
)
};
self
}
pub fn decrypt_unverified_into(&mut self, text: &[u8], output: &mut [u8]) -> &mut Self {
assert_eq!(text.len(), output.len(), "`output` must be as long as `text`");
unsafe {
sz_aes256_gcm_decryptor_update_unverified(
self as *mut _ as *mut c_void,
text.as_ptr() as *const c_void,
text.len(),
output.as_mut_ptr() as *mut c_void,
)
};
self
}
pub fn decrypt_unverified_in_place(&mut self, text: &mut [u8]) -> &mut Self {
let length = text.len();
let pointer = text.as_mut_ptr() as *mut c_void;
unsafe {
sz_aes256_gcm_decryptor_update_unverified(
self as *mut _ as *mut c_void,
pointer as *const c_void,
length,
pointer,
)
};
self
}
pub fn verify(&self, tag: &[u8; AES256_TAG_LENGTH]) -> Result<(), AuthenticationError> {
let status = unsafe { sz_aes256_gcm_decryptor_verify(self as *const _ as *const c_void, tag.as_ptr()) };
authentication_result_from_status(status)
}
}
pub fn dynamic_dispatch() -> bool {
unsafe { sz_dynamic_dispatch() != 0 }
}
pub fn version() -> SemVer {
SemVer {
major: unsafe { sz_version_major() },
minor: unsafe { sz_version_minor() },
patch: unsafe { sz_version_patch() },
}
}
pub struct FixedCString<const CAPACITY: usize> {
buf: [u8; CAPACITY],
len: usize,
}
impl<const CAPACITY: usize> FixedCString<CAPACITY> {
pub const fn new() -> Self {
Self {
buf: [0u8; CAPACITY],
len: 0,
}
}
pub const fn as_ptr(&self) -> *const u8 {
self.buf.as_ptr()
}
pub fn as_c_str(&self) -> &CStr {
unsafe { CStr::from_bytes_with_nul_unchecked(&self.buf[..=self.len]) }
}
pub fn as_str(&self) -> &str {
core::str::from_utf8(&self.buf[..self.len]).unwrap_or("")
}
}
impl<const CAPACITY: usize> Default for FixedCString<CAPACITY> {
fn default() -> Self {
Self::new()
}
}
impl<const CAPACITY: usize> Write for FixedCString<CAPACITY> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let bytes = s.as_bytes();
if self.len + bytes.len() >= CAPACITY {
return Err(fmt::Error);
}
self.buf[self.len..self.len + bytes.len()].copy_from_slice(bytes);
self.len += bytes.len();
self.buf[self.len] = 0;
Ok(())
}
}
pub type SmallCString = FixedCString<256>;
pub(crate) fn capabilities_from_enum(caps: u32) -> SmallCString {
let caps_ptr = unsafe { sz_capabilities_to_string(caps) };
let cstr = unsafe { CStr::from_ptr(caps_ptr as *const c_char) };
let bytes = cstr.to_bytes();
let mut buf = SmallCString::new();
let s = core::str::from_utf8(bytes).unwrap_or("");
let _ = buf.write_str(s);
buf
}
pub fn capabilities() -> SmallCString {
let caps = unsafe { sz_capabilities() };
capabilities_from_enum(caps)
}
#[inline(always)]
pub fn bytesum<Text>(text: Text) -> u64
where
Text: AsRef<[u8]>,
{
let text_ref = text.as_ref();
let text_pointer = text_ref.as_ptr() as _;
let text_length = text_ref.len();
unsafe { sz_bytesum(text_pointer, text_length) }
}
#[inline(always)]
pub fn move_<Target, Source>(target: &mut Target, source: &Source)
where
Target: AsMut<[u8]> + ?Sized,
Source: AsRef<[u8]> + ?Sized,
{
let target_slice = target.as_mut();
let source_slice = source.as_ref();
assert!(
target_slice.len() >= source_slice.len(),
"target must be at least as long as source"
);
unsafe {
sz_move(
target_slice.as_mut_ptr() as *const c_void,
source_slice.as_ptr() as *const c_void,
source_slice.len(),
);
}
}
#[inline(always)]
pub fn fill<Target>(target: &mut Target, value: u8)
where
Target: AsMut<[u8]> + ?Sized,
{
let target_slice = target.as_mut();
unsafe {
sz_fill(target_slice.as_ptr() as *const c_void, target_slice.len(), value);
}
}
#[inline(always)]
pub fn copy<Target, Source>(target: &mut Target, source: &Source)
where
Target: AsMut<[u8]> + ?Sized,
Source: AsRef<[u8]> + ?Sized,
{
let target_slice = target.as_mut();
let source_slice = source.as_ref();
assert!(
target_slice.len() >= source_slice.len(),
"target must be at least as long as source"
);
unsafe {
sz_copy(
target_slice.as_mut_ptr() as *mut c_void,
source_slice.as_ptr() as *const c_void,
source_slice.len(),
);
}
}
pub fn lookup<Target, Source>(target: &mut Target, source: &Source, table: [u8; 256])
where
Target: AsMut<[u8]> + ?Sized,
Source: AsRef<[u8]> + ?Sized,
{
let target_slice = target.as_mut();
let source_slice = source.as_ref();
assert!(
target_slice.len() >= source_slice.len(),
"target must be at least as long as source"
);
unsafe {
sz_lookup(
target_slice.as_mut_ptr() as *mut c_void,
source_slice.len(),
source_slice.as_ptr() as *const c_void,
table.as_ptr() as _,
);
}
}
pub fn lookup_inplace<Buffer>(buffer: &mut Buffer, table: [u8; 256])
where
Buffer: AsMut<[u8]> + ?Sized,
{
let buffer_slice = buffer.as_mut();
unsafe {
sz_lookup(
buffer_slice.as_mut_ptr() as *mut c_void,
buffer_slice.len(),
buffer_slice.as_ptr() as *const c_void,
table.as_ptr() as _,
);
}
}
pub fn utf8_uncased_fold<Source, Destination>(source: Source, destination: &mut Destination) -> usize
where
Source: AsRef<[u8]>,
Destination: AsMut<[u8]> + ?Sized,
{
let source_ref = source.as_ref();
let dest_slice = destination.as_mut();
unsafe {
sz_utf8_uncased_fold(
source_ref.as_ptr() as *const c_void,
source_ref.len(),
dest_slice.as_mut_ptr() as *mut c_void,
)
}
}
pub fn utf8_norm<Source, Destination>(source: Source, form: Utf8NormalForm, destination: &mut Destination) -> usize
where
Source: AsRef<[u8]>,
Destination: AsMut<[u8]> + ?Sized,
{
let source_ref = source.as_ref();
let dest_slice = destination.as_mut();
unsafe {
sz_utf8_norm(
source_ref.as_ptr() as *const c_void,
source_ref.len(),
form as i32,
dest_slice.as_mut_ptr() as *mut c_void,
)
}
}
pub fn utf8_find_denormalized<Source>(source: Source, form: Utf8NormalForm) -> Option<usize>
where
Source: AsRef<[u8]>,
{
let source_ref = source.as_ref();
let ptr = unsafe { sz_utf8_find_denormalized(source_ref.as_ptr() as *const c_void, source_ref.len(), form as i32) };
if ptr.is_null() {
None
} else {
let offset = unsafe { (ptr as *const u8).offset_from(source_ref.as_ptr()) } as usize;
Some(offset)
}
}
pub fn utf8_uncased_search<Haystack, Needle>(haystack: Haystack, needle: Needle) -> Option<(usize, usize)>
where
Haystack: AsRef<[u8]>,
Needle: Utf8UncasedNeedleArg,
{
needle.find_uncased_in(haystack.as_ref())
}
pub trait Utf8UncasedNeedleArg {
fn find_uncased_in(self, haystack: &[u8]) -> Option<(usize, usize)>;
}
impl<Source: AsRef<[u8]>> Utf8UncasedNeedleArg for Source {
fn find_uncased_in(self, haystack: &[u8]) -> Option<(usize, usize)> {
let needle_ref = self.as_ref();
let mut matched_length: usize = 0;
let mut needle_metadata = Utf8UncasedNeedleMetadata::default();
let result = unsafe {
sz_utf8_uncased_search(
haystack.as_ptr() as *const c_void,
haystack.len(),
needle_ref.as_ptr() as *const c_void,
needle_ref.len(),
&mut needle_metadata,
&mut matched_length,
)
};
if result.is_null() {
None
} else {
let offset = unsafe { result.offset_from(haystack.as_ptr() as *const c_void) };
Some((offset as usize, matched_length))
}
}
}
impl<'a, 'b> Utf8UncasedNeedleArg for &'b Utf8UncasedNeedle<'a> {
fn find_uncased_in(self, haystack: &[u8]) -> Option<(usize, usize)> {
let needle_bytes = self.as_bytes();
let mut matched_length: usize = 0;
let result = unsafe {
sz_utf8_uncased_search(
haystack.as_ptr() as *const c_void,
haystack.len(),
needle_bytes.as_ptr() as *const c_void,
needle_bytes.len(),
&mut *self.metadata_ptr(),
&mut matched_length,
)
};
if result.is_null() {
None
} else {
let offset = unsafe { result.offset_from(haystack.as_ptr() as *const c_void) };
Some((offset as usize, matched_length))
}
}
}
pub fn utf8_uncased_order<First, Second>(first: First, second: Second) -> Ordering
where
First: AsRef<[u8]>,
Second: AsRef<[u8]>,
{
let first_ref = first.as_ref();
let second_ref = second.as_ref();
let result = unsafe {
sz_utf8_uncased_order(
first_ref.as_ptr() as *const c_void,
first_ref.len(),
second_ref.as_ptr() as *const c_void,
second_ref.len(),
)
};
match result {
x if x < 0 => Ordering::Less,
0 => Ordering::Equal,
_ => Ordering::Greater,
}
}
pub fn order<First, Second>(first: First, second: Second) -> Ordering
where
First: AsRef<[u8]>,
Second: AsRef<[u8]>,
{
let first_ref = first.as_ref();
let second_ref = second.as_ref();
let result = unsafe {
sz_order(
first_ref.as_ptr() as *const c_void,
first_ref.len(),
second_ref.as_ptr() as *const c_void,
second_ref.len(),
)
};
match result {
x if x < 0 => Ordering::Less,
0 => Ordering::Equal,
_ => Ordering::Greater,
}
}
pub fn equal<First, Second>(first: First, second: Second) -> bool
where
First: AsRef<[u8]>,
Second: AsRef<[u8]>,
{
let first_ref = first.as_ref();
let second_ref = second.as_ref();
first_ref.len() == second_ref.len()
&& unsafe {
sz_equal(
first_ref.as_ptr() as *const c_void,
second_ref.as_ptr() as *const c_void,
first_ref.len(),
) != 0
}
}
pub fn utf8_decode(text: &[u8], runes: &mut [u32]) -> (usize, usize) {
let mut runes_unpacked: usize = 0;
let result = unsafe {
sz_utf8_decode(
text.as_ptr() as *const c_void,
text.len(),
runes.as_mut_ptr(),
runes.len(),
&mut runes_unpacked,
)
};
let bytes_consumed = if result.is_null() {
0
} else {
unsafe { result.offset_from(text.as_ptr() as *const c_void) as usize }
};
(bytes_consumed, runes_unpacked)
}
#[inline(always)]
pub fn hash_with_seed<Text>(text: Text, seed: u64) -> u64
where
Text: AsRef<[u8]>,
{
let text_ref = text.as_ref();
let text_pointer = text_ref.as_ptr() as _;
let text_length = text_ref.len();
unsafe { sz_hash(text_pointer, text_length, seed) }
}
#[inline(always)]
pub fn hash<Text>(text: Text) -> u64
where
Text: AsRef<[u8]>,
{
hash_with_seed(text, 0)
}
#[inline(always)]
pub fn hash_multiseed_into<Text>(text: Text, seeds: &[u64], out: &mut [u64])
where
Text: AsRef<[u8]>,
{
assert_eq!(seeds.len(), out.len(), "`out` must have one slot per seed");
let text_ref = text.as_ref();
unsafe {
sz_hash_multiseed(
text_ref.as_ptr() as _,
text_ref.len(),
seeds.as_ptr(),
seeds.len(),
out.as_mut_ptr(),
)
}
}
pub fn find<Haystack, Needle>(haystack: Haystack, needle: Needle) -> Option<usize>
where
Haystack: AsRef<[u8]>,
Needle: AsRef<[u8]>,
{
let haystack_ref = haystack.as_ref();
let needle_ref = needle.as_ref();
let haystack_pointer = haystack_ref.as_ptr() as _;
let haystack_length = haystack_ref.len();
let needle_pointer = needle_ref.as_ptr() as _;
let needle_length = needle_ref.len();
let result = unsafe { sz_find(haystack_pointer, haystack_length, needle_pointer, needle_length) };
if result.is_null() {
None
} else {
Some(unsafe { result.offset_from(haystack_pointer) }.try_into().unwrap())
}
}
#[inline(always)]
pub fn rfind<Haystack, Needle>(haystack: Haystack, needle: Needle) -> Option<usize>
where
Haystack: AsRef<[u8]>,
Needle: AsRef<[u8]>,
{
let haystack_ref = haystack.as_ref();
let needle_ref = needle.as_ref();
let haystack_pointer = haystack_ref.as_ptr() as _;
let haystack_length = haystack_ref.len();
let needle_pointer = needle_ref.as_ptr() as _;
let needle_length = needle_ref.len();
let result = unsafe { sz_rfind(haystack_pointer, haystack_length, needle_pointer, needle_length) };
if result.is_null() {
None
} else {
Some(unsafe { result.offset_from(haystack_pointer) }.try_into().unwrap())
}
}
#[inline(always)]
pub fn contains<Haystack, Needle>(haystack: Haystack, needle: Needle) -> bool
where
Haystack: AsRef<[u8]>,
Needle: AsRef<[u8]>,
{
find(haystack, needle).is_some()
}
#[inline(always)]
pub fn find_byteset<Haystack>(haystack: Haystack, needles: Byteset) -> Option<usize>
where
Haystack: AsRef<[u8]>,
{
let haystack_ref = haystack.as_ref();
let haystack_pointer = haystack_ref.as_ptr() as _;
let haystack_length = haystack_ref.len();
let result = unsafe { sz_find_byteset(haystack_pointer, haystack_length, &needles as *const _ as *const c_void) };
if result.is_null() {
None
} else {
Some(unsafe { result.offset_from(haystack_pointer) }.try_into().unwrap())
}
}
pub fn rfind_byteset<Haystack>(haystack: Haystack, needles: Byteset) -> Option<usize>
where
Haystack: AsRef<[u8]>,
{
let haystack_ref = haystack.as_ref();
let haystack_pointer = haystack_ref.as_ptr() as _;
let haystack_length = haystack_ref.len();
let result = unsafe { sz_rfind_byteset(haystack_pointer, haystack_length, &needles as *const _ as *const c_void) };
if result.is_null() {
None
} else {
Some(unsafe { result.offset_from(haystack_pointer) }.try_into().unwrap())
}
}
#[inline(always)]
pub fn find_byte_from<Haystack, Needle>(haystack: Haystack, needles: Needle) -> Option<usize>
where
Haystack: AsRef<[u8]>,
Needle: AsRef<[u8]>,
{
find_byteset(haystack, Byteset::from(needles))
}
pub fn rfind_byte_from<Haystack, Needle>(haystack: Haystack, needles: Needle) -> Option<usize>
where
Haystack: AsRef<[u8]>,
Needle: AsRef<[u8]>,
{
rfind_byteset(haystack, Byteset::from(needles))
}
pub fn find_byte_not_from<Haystack, Needle>(haystack: Haystack, needles: Needle) -> Option<usize>
where
Haystack: AsRef<[u8]>,
Needle: AsRef<[u8]>,
{
find_byteset(haystack, Byteset::from(needles).inverted())
}
pub fn rfind_byte_not_from<Haystack, Needle>(haystack: Haystack, needles: Needle) -> Option<usize>
where
Haystack: AsRef<[u8]>,
Needle: AsRef<[u8]>,
{
rfind_byteset(haystack, Byteset::from(needles).inverted())
}
#[cfg(feature = "std")]
fn replace_all_with_finder<FindNext, FindPrev>(
buffer: &mut Vec<u8>,
needle_length: usize,
replacement: &[u8],
mut find_next: FindNext,
mut find_prev: FindPrev,
) -> Result<usize, Status>
where
FindNext: FnMut(&[u8], usize) -> Option<usize>,
FindPrev: FnMut(&[u8], usize) -> Option<usize>,
{
if needle_length == 0 || buffer.is_empty() {
return Ok(0);
}
if needle_length == replacement.len() {
let mut replaced = 0;
let mut search_from = 0;
while let Some(pos) = find_next(buffer.as_slice(), search_from) {
copy(&mut buffer[pos..pos + needle_length], &replacement);
search_from = pos + needle_length;
replaced += 1;
}
return Ok(replaced);
}
if needle_length > replacement.len() {
let mut replaced = 0;
let mut read = 0;
let mut write = 0;
let len = buffer.len();
while let Some(pos) = find_next(buffer.as_slice(), read) {
if pos > read {
let chunk = pos - read;
unsafe {
sz_move(
buffer.as_mut_ptr().add(write) as *const c_void,
buffer.as_ptr().add(read) as *const c_void,
chunk,
);
}
write += chunk;
}
copy(&mut buffer[write..write + replacement.len()], replacement);
write += replacement.len();
read = pos + needle_length;
replaced += 1;
}
if read < len {
let chunk = len - read;
unsafe {
sz_move(
buffer.as_mut_ptr().add(write) as *const c_void,
buffer.as_ptr().add(read) as *const c_void,
chunk,
);
}
write += len - read;
}
buffer.truncate(write);
return Ok(replaced);
}
let mut match_count = 0usize;
let mut search_from = 0;
while let Some(pos) = find_next(buffer.as_slice(), search_from) {
match_count += 1;
search_from = pos + needle_length;
}
if match_count == 0 {
return Ok(0);
}
let original_len = buffer.len();
let delta = replacement.len() - needle_length;
let added = match match_count.checked_mul(delta) {
Some(v) => v,
None => return Err(Status::OverflowRisk),
};
let new_len = match original_len.checked_add(added) {
Some(v) => v,
None => return Err(Status::OverflowRisk),
};
if let Err(_) = buffer.try_reserve_exact(added) {
return Err(Status::BadAlloc);
}
buffer.resize(new_len, 0);
let mut read_end = original_len;
let mut write_end = new_len;
while let Some(pos) = find_prev(buffer.as_slice(), read_end) {
let match_end = pos + needle_length;
let tail_len = read_end - match_end;
if tail_len > 0 {
unsafe {
sz_move(
buffer.as_mut_ptr().add(write_end - tail_len) as *const c_void,
buffer.as_ptr().add(match_end) as *const c_void,
tail_len,
);
}
}
write_end -= tail_len;
write_end -= replacement.len();
copy(&mut buffer[write_end..write_end + replacement.len()], replacement);
read_end = pos;
}
debug_assert_eq!(write_end, read_end, "replace_all backfill mismatch");
Ok(match_count)
}
#[cfg(feature = "std")]
pub fn try_replace_all(buffer: &mut Vec<u8>, needle: &[u8], replacement: &[u8]) -> Result<usize, Status> {
replace_all_with_finder(
buffer,
needle.len(),
replacement,
|haystack, start| {
if start >= haystack.len() {
None
} else {
find(&haystack[start..], needle).map(|offset| start + offset)
}
},
|haystack, end| {
if end == 0 {
None
} else {
rfind(&haystack[..end], needle)
}
},
)
}
#[cfg(feature = "std")]
pub fn try_replace_all_byteset(buffer: &mut Vec<u8>, byteset: Byteset, replacement: &[u8]) -> Result<usize, Status> {
if byteset.bits.iter().all(|&b| b == 0) {
return Ok(0);
}
replace_all_with_finder(
buffer,
1,
replacement,
|haystack, start| {
if start >= haystack.len() {
None
} else {
find_byteset(&haystack[start..], byteset).map(|offset| start + offset)
}
},
|haystack, end| {
if end == 0 {
None
} else {
rfind_byteset(&haystack[..end], byteset)
}
},
)
}
pub fn count_utf8<Text>(text: Text) -> usize
where
Text: AsRef<[u8]>,
{
let text_ref = text.as_ref();
let text_pointer = text_ref.as_ptr() as *const c_void;
let text_length = text_ref.len();
unsafe { sz_utf8_count(text_pointer, text_length) }
}
pub fn find_nth_utf8<Text>(text: Text, n: usize) -> Option<usize>
where
Text: AsRef<[u8]>,
{
let text_ref = text.as_ref();
let text_pointer = text_ref.as_ptr() as *const c_void;
let text_length = text_ref.len();
let result = unsafe { sz_utf8_seek(text_pointer, text_length, n) };
if result.is_null() {
None
} else {
let offset = unsafe { (result as *const u8).offset_from(text_pointer as *const u8) }
.try_into()
.unwrap();
Some(offset)
}
}
pub struct Utf8View<'a> {
octets: &'a [u8],
cached_len: core::cell::Cell<Option<usize>>,
}
impl<'a> Utf8View<'a> {
pub const fn new(octets: &'a [u8]) -> Self {
Self {
octets,
cached_len: core::cell::Cell::new(None),
}
}
pub fn len(&self) -> usize {
if let Some(len) = self.cached_len.get() {
return len;
}
let len = count_utf8(self.octets);
self.cached_len.set(Some(len));
len
}
pub const fn is_empty(&self) -> bool {
self.octets.is_empty()
}
pub fn offset_of(&self, n: usize) -> Option<usize> {
find_nth_utf8(self.octets, n)
}
pub fn iter(&self) -> Utf8Runes<'a> {
Utf8Runes::new(self.octets)
}
}
pub struct Utf8Runes<'a, const STEPS: usize = ITERATORS_DEFAULT_STEPS> {
octets: &'a [u8],
octets_offset: usize,
runes: [u32; STEPS], runes_count: usize, runes_offset: usize, }
impl<'a> Utf8Runes<'a, ITERATORS_DEFAULT_STEPS> {
fn new(octets: &'a [u8]) -> Self {
Self::with_steps(octets)
}
}
impl<'a, const STEPS: usize> Utf8Runes<'a, STEPS> {
pub fn with_steps(octets: &'a [u8]) -> Self {
let mut iter = Self {
octets,
octets_offset: 0,
runes: [0; STEPS],
runes_count: 0,
runes_offset: 0,
};
iter.decode_batch();
iter
}
fn decode_batch(&mut self) {
if self.octets_offset >= self.octets.len() {
self.runes_count = 0;
return;
}
let octets_ptr = unsafe { self.octets.as_ptr().add(self.octets_offset) as *const c_void };
let mut unpacked_count: usize = 0;
let next_ptr = unsafe {
sz_utf8_decode(
octets_ptr,
self.octets.len() - self.octets_offset,
self.runes.as_mut_ptr(),
STEPS,
&mut unpacked_count as *mut usize,
)
};
let bytes_consumed: usize = unsafe {
let offset = (next_ptr as *const u8).offset_from(octets_ptr as *const u8);
debug_assert!(offset >= 0, "sz_utf8_decode returned a pointer before the input");
offset.try_into().expect("offset should be non-negative")
};
self.octets_offset += bytes_consumed;
self.runes_offset = 0;
if unpacked_count == 0 && self.octets_offset < self.octets.len() {
self.runes[0] = 0xFFFD;
self.runes_count = 1;
self.octets_offset = self.octets.len();
} else {
self.runes_count = unpacked_count;
}
}
}
impl<'a, const STEPS: usize> Iterator for Utf8Runes<'a, STEPS> {
type Item = char;
fn next(&mut self) -> Option<char> {
if self.runes_offset >= self.runes_count {
self.decode_batch();
if self.runes_count == 0 {
return None;
}
}
let codepoint = self.runes[self.runes_offset];
self.runes_offset += 1;
Some(unsafe { char::from_u32_unchecked(codepoint) })
}
fn size_hint(&self) -> (usize, Option<usize>) {
let lower = self.runes_count.saturating_sub(self.runes_offset);
(lower, None)
}
}
pub fn fill_random<Buffer>(buffer: &mut Buffer, nonce: u64)
where
Buffer: AsMut<[u8]> + ?Sized, {
let buffer_slice = buffer.as_mut();
unsafe {
sz_fill_random(buffer_slice.as_ptr() as _, buffer_slice.len(), nonce);
}
}
struct _PunnedSliceLookupView {
get_slice: unsafe fn(*const c_void, usize) -> &'static [u8],
data: *const c_void,
}
unsafe extern "C" fn _slice_get_start_punned(handle: *const c_void, idx: SortedIdx) -> *const c_void {
let view = &*(handle as *const _PunnedSliceLookupView);
let slice = (view.get_slice)(view.data, idx);
slice.as_ptr() as *const c_void
}
unsafe extern "C" fn _slice_get_length_punned(handle: *const c_void, idx: SortedIdx) -> usize {
let view = &*(handle as *const _PunnedSliceLookupView);
let slice = (view.get_slice)(view.data, idx);
slice.len()
}
unsafe fn _get_slice_fn<Mapper>() -> unsafe fn(*const c_void, usize) -> &'static [u8]
where
Mapper: Fn(usize) -> &'static [u8],
{
unsafe fn get_slice_impl<Mapper>(data: *const c_void, idx: usize) -> &'static [u8]
where
Mapper: Fn(usize) -> &'static [u8],
{
let mapper = &*(data as *const Mapper);
mapper(idx)
}
get_slice_impl::<Mapper>
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ArgsortOptions {
pub reverse: bool,
pub uncased: bool,
pub top: Option<usize>,
}
impl ArgsortOptions {
pub const fn reversed(mut self) -> Self {
self.reverse = true;
self
}
pub const fn uncased(mut self) -> Self {
self.uncased = true;
self
}
pub const fn top(mut self, count: usize) -> Self {
self.top = Some(count);
self
}
}
pub fn argsort<Element: AsRef<[u8]>>(
data: &[Element],
order: &mut [SortedIdx],
options: ArgsortOptions,
) -> Result<(), Status> {
if data.len() > order.len() {
return Err(Status::BadAlloc);
}
argsort_by(|i| data[i].as_ref(), &mut order[..data.len()], options)
}
pub fn argsort_by<Mapper, Key>(mapper: Mapper, order: &mut [SortedIdx], options: ArgsortOptions) -> Result<(), Status>
where
Mapper: Fn(usize) -> Key,
Key: AsRef<[u8]>,
{
let adapter = move |i: usize| -> &'static [u8] {
let binding = mapper(i);
let slice = binding.as_ref();
unsafe { core::mem::transmute(slice) }
};
_argsort_impl(adapter, order, options)
}
fn _argsort_impl<Adapter>(adapter: Adapter, order: &mut [SortedIdx], options: ArgsortOptions) -> Result<(), Status>
where
Adapter: Fn(usize) -> &'static [u8],
{
let wrapper = _PunnedSliceLookupView {
get_slice: unsafe { _get_slice_fn::<Adapter>() },
data: &adapter as *const Adapter as *const c_void,
};
let seq = _SzSequence {
handle: &wrapper as *const _ as *const c_void,
count: order.len(),
get_start: Some(_slice_get_start_punned),
get_length: Some(_slice_get_length_punned),
};
let top_count = options.top.unwrap_or(0);
let reverse = options.reverse as i32;
let status = unsafe {
if options.uncased {
sz_sequence_argsort_uncased(&seq, core::ptr::null(), order.as_mut_ptr(), top_count, reverse)
} else {
sz_sequence_argsort(&seq, core::ptr::null(), order.as_mut_ptr(), top_count, reverse)
}
};
if status == Status::Success {
Ok(())
} else {
Err(status)
}
}
pub fn intersection<Element: AsRef<[u8]>>(
data1: &[Element],
data2: &[Element],
seed: u64,
positions1: &mut [SortedIdx],
positions2: &mut [SortedIdx],
) -> Result<usize, Status> {
let min_count = data1.len().min(data2.len());
if positions1.len() < min_count || positions2.len() < min_count {
return Err(Status::BadAlloc);
}
let adapter1 = move |i: usize| -> &'static [u8] {
unsafe { core::mem::transmute::<&[u8], &'static [u8]>(data1[i].as_ref()) }
};
let adapter2 = move |j: usize| -> &'static [u8] {
unsafe { core::mem::transmute::<&[u8], &'static [u8]>(data2[j].as_ref()) }
};
_intersection_by_impl(
adapter1,
adapter2,
seed,
positions1,
positions2,
data1.len(),
data2.len(),
)
}
pub fn intersection_by<Mapper1, Mapper2, Key1, Key2>(
mapper1: Mapper1,
mapper2: Mapper2,
seed: u64,
positions1: &mut [SortedIdx],
positions2: &mut [SortedIdx],
) -> Result<usize, Status>
where
Mapper1: Fn(usize) -> Key1,
Key1: AsRef<[u8]>,
Mapper2: Fn(usize) -> Key2,
Key2: AsRef<[u8]>,
{
if positions1.len() != positions2.len() {
return Err(Status::BadAlloc);
}
let adapter1 = move |i: usize| -> &'static [u8] {
let binding = mapper1(i);
let slice = binding.as_ref();
unsafe { core::mem::transmute(slice) }
};
let adapter2 = move |i: usize| -> &'static [u8] {
let binding = mapper2(i);
let slice = binding.as_ref();
unsafe { core::mem::transmute(slice) }
};
_intersection_by_impl(
adapter1,
adapter2,
seed,
positions1,
positions2,
positions1.len(),
positions2.len(),
)
}
fn _intersection_by_impl<Adapter1, Adapter2>(
adapter1: Adapter1,
adapter2: Adapter2,
seed: u64,
positions1: &mut [SortedIdx],
positions2: &mut [SortedIdx],
count1: usize,
count2: usize,
) -> Result<usize, Status>
where
Adapter1: Fn(usize) -> &'static [u8],
Adapter2: Fn(usize) -> &'static [u8],
{
let wrapper1 = _PunnedSliceLookupView {
get_slice: unsafe { _get_slice_fn::<Adapter1>() },
data: &adapter1 as *const Adapter1 as *const c_void,
};
let wrapper2 = _PunnedSliceLookupView {
get_slice: unsafe { _get_slice_fn::<Adapter2>() },
data: &adapter2 as *const Adapter2 as *const c_void,
};
let seq1 = _SzSequence {
handle: &wrapper1 as *const _ as *const c_void,
count: count1,
get_start: Some(_slice_get_start_punned),
get_length: Some(_slice_get_length_punned),
};
let seq2 = _SzSequence {
handle: &wrapper2 as *const _ as *const c_void,
count: count2,
get_start: Some(_slice_get_start_punned),
get_length: Some(_slice_get_length_punned),
};
let mut inter_size: usize = 0;
let status = unsafe {
sz_sequence_intersect(
&seq1,
&seq2,
core::ptr::null(),
seed,
&mut inter_size as *mut usize,
positions1.as_mut_ptr(),
positions2.as_mut_ptr(),
)
};
if status == Status::Success {
Ok(inter_size)
} else {
Err(status)
}
}
pub trait Matcher<'a> {
fn find(&self, haystack: &'a [u8]) -> Option<usize>;
fn needle_length(&self) -> usize;
}
pub enum MatcherType<'a> {
Find(&'a [u8]),
RFind(&'a [u8]),
FindFirstOf(&'a [u8]),
FindLastOf(&'a [u8]),
FindFirstNotOf(&'a [u8]),
FindLastNotOf(&'a [u8]),
}
impl<'a> Matcher<'a> for MatcherType<'a> {
fn find(&self, haystack: &'a [u8]) -> Option<usize> {
match self {
MatcherType::Find(needle) => find(haystack, needle),
MatcherType::RFind(needle) => rfind(haystack, needle),
MatcherType::FindFirstOf(needles) => find_byte_from(haystack, needles),
MatcherType::FindLastOf(needles) => rfind_byte_from(haystack, needles),
MatcherType::FindFirstNotOf(needles) => find_byte_not_from(haystack, needles),
MatcherType::FindLastNotOf(needles) => rfind_byte_not_from(haystack, needles),
}
}
fn needle_length(&self) -> usize {
match self {
MatcherType::Find(needle) | MatcherType::RFind(needle) => needle.len(),
_ => 1,
}
}
}
pub struct FindMatches<'a, Overlap: Overlaps = NonOverlapping> {
haystack: &'a [u8],
matcher: MatcherType<'a>,
position: usize,
_overlaps: PhantomData<Overlap>,
}
impl<'a> FindMatches<'a, NonOverlapping> {
pub fn new(haystack: &'a [u8], matcher: MatcherType<'a>) -> Self {
Self {
haystack,
matcher,
position: 0,
_overlaps: PhantomData,
}
}
pub fn overlapping(self) -> FindMatches<'a, Overlapping> {
FindMatches {
haystack: self.haystack,
matcher: self.matcher,
position: self.position,
_overlaps: PhantomData,
}
}
}
impl<'a, Overlap: Overlaps> Iterator for FindMatches<'a, Overlap> {
type Item = &'a [u8];
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
if self.position > self.haystack.len() {
return None;
}
if let Some(index) = self.matcher.find(&self.haystack[self.position..]) {
debug_assert!(
self.position + index + self.matcher.needle_length() <= self.haystack.len(),
"matcher returned a match span past the haystack end"
);
let start = self.position + index;
let end = start + self.matcher.needle_length();
let step = if Overlap::OVERLAP {
1
} else {
self.matcher.needle_length().max(1)
};
self.position = start + step;
Some(&self.haystack[start..end])
} else {
self.position = self.haystack.len() + 1;
None
}
}
}
pub struct FindSplits<'a, Empty: EmptySegments = KeepEmpty, const STEPS: usize = ITERATORS_DEFAULT_STEPS> {
haystack: &'a [u8],
matcher: MatcherType<'a>,
position: usize,
_empties: PhantomData<Empty>,
}
impl<'a> FindSplits<'a, KeepEmpty, ITERATORS_DEFAULT_STEPS> {
pub fn new(haystack: &'a [u8], matcher: MatcherType<'a>) -> Self {
Self::with_steps(haystack, matcher)
}
}
impl<'a, const STEPS: usize> FindSplits<'a, KeepEmpty, STEPS> {
pub fn with_steps(haystack: &'a [u8], matcher: MatcherType<'a>) -> Self {
Self {
haystack,
matcher,
position: 0,
_empties: PhantomData,
}
}
pub fn skip_empty(self) -> FindSplits<'a, SkipEmpty, STEPS> {
FindSplits {
haystack: self.haystack,
matcher: self.matcher,
position: self.position,
_empties: PhantomData,
}
}
}
impl<'a, Empty: EmptySegments, const STEPS: usize> FindSplits<'a, Empty, STEPS> {
#[inline(always)]
fn next_raw(&mut self) -> Option<&'a [u8]> {
if self.matcher.needle_length() == 0 {
if self.position > self.haystack.len() {
return None;
}
self.position = self.haystack.len() + 1;
return Some(self.haystack);
}
if self.position > self.haystack.len() {
return None;
}
if let Some(index) = self.matcher.find(&self.haystack[self.position..]) {
debug_assert!(
self.position + index + self.matcher.needle_length() <= self.haystack.len(),
"matcher returned a match span past the haystack end"
);
let start = self.position;
let end = self.position + index;
self.position = end + self.matcher.needle_length().max(1);
Some(&self.haystack[start..end])
} else {
let start = self.position;
self.position = self.haystack.len() + 1;
Some(&self.haystack[start..])
}
}
}
impl<'a, Empty: EmptySegments, const STEPS: usize> Iterator for FindSplits<'a, Empty, STEPS> {
type Item = &'a [u8];
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
loop {
let segment = self.next_raw()?;
if Empty::SKIP && segment.is_empty() {
continue;
}
return Some(segment);
}
}
}
pub struct RFindMatches<'a, Overlap: Overlaps = NonOverlapping> {
haystack: &'a [u8],
matcher: MatcherType<'a>,
position: usize,
_overlaps: PhantomData<Overlap>,
}
impl<'a> RFindMatches<'a, NonOverlapping> {
pub fn new(haystack: &'a [u8], matcher: MatcherType<'a>) -> Self {
Self {
haystack,
matcher,
position: haystack.len(),
_overlaps: PhantomData,
}
}
pub fn overlapping(self) -> RFindMatches<'a, Overlapping> {
RFindMatches {
haystack: self.haystack,
matcher: self.matcher,
position: self.position,
_overlaps: PhantomData,
}
}
}
impl<'a, Overlap: Overlaps> Iterator for RFindMatches<'a, Overlap> {
type Item = &'a [u8];
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
if self.position == usize::MAX {
return None;
}
let previous_position = self.position;
let search_area = &self.haystack[..self.position];
if let Some(index) = self.matcher.find(search_area) {
let start = index;
let end = start + self.matcher.needle_length();
let result = Some(&self.haystack[start..end]);
let skip = if Overlap::OVERLAP {
self.matcher.needle_length().saturating_sub(1)
} else {
0
};
let next_position = start + skip;
self.position = if next_position < previous_position {
next_position
} else if next_position == 0 {
usize::MAX
} else {
next_position - 1
};
result
} else {
None
}
}
}
pub struct RFindSplits<'a, Empty: EmptySegments = KeepEmpty, const STEPS: usize = ITERATORS_DEFAULT_STEPS> {
haystack: &'a [u8],
matcher: MatcherType<'a>,
position: Option<usize>, _empties: PhantomData<Empty>,
}
impl<'a> RFindSplits<'a, KeepEmpty, ITERATORS_DEFAULT_STEPS> {
pub fn new(haystack: &'a [u8], matcher: MatcherType<'a>) -> Self {
Self::with_steps(haystack, matcher)
}
}
impl<'a, const STEPS: usize> RFindSplits<'a, KeepEmpty, STEPS> {
pub fn with_steps(haystack: &'a [u8], matcher: MatcherType<'a>) -> Self {
Self {
haystack,
matcher,
position: Some(haystack.len()),
_empties: PhantomData,
}
}
pub fn skip_empty(self) -> RFindSplits<'a, SkipEmpty, STEPS> {
RFindSplits {
haystack: self.haystack,
matcher: self.matcher,
position: self.position,
_empties: PhantomData,
}
}
}
impl<'a, Empty: EmptySegments, const STEPS: usize> RFindSplits<'a, Empty, STEPS> {
#[inline(always)]
fn next_raw(&mut self) -> Option<&'a [u8]> {
let position = self.position?;
if self.matcher.needle_length() == 0 {
self.position = None;
return Some(&self.haystack[..position]);
}
let search_area = &self.haystack[..position];
if let Some(index) = self.matcher.find(search_area) {
let start = index + self.matcher.needle_length();
self.position = if index < position {
Some(index)
} else {
index.checked_sub(1)
};
Some(&self.haystack[start..position])
} else {
self.position = None;
Some(&self.haystack[..position])
}
}
}
impl<'a, Empty: EmptySegments, const STEPS: usize> Iterator for RFindSplits<'a, Empty, STEPS> {
type Item = &'a [u8];
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
loop {
let segment = self.next_raw()?;
if Empty::SKIP && segment.is_empty() {
continue;
}
return Some(segment);
}
}
}
use core::marker::PhantomData;
pub trait SegmenterKernel {
unsafe fn segment(
text: *const c_void,
length: usize,
offsets: *mut usize,
lengths: *mut usize,
capacity: usize,
consumed: *mut usize,
) -> usize;
}
pub struct Newlines;
impl SegmenterKernel for Newlines {
unsafe fn segment(t: *const c_void, n: usize, o: *mut usize, l: *mut usize, c: usize, u: *mut usize) -> usize {
sz_utf8_newlines(t, n, o, l, c, u)
}
}
pub struct Whitespaces;
impl SegmenterKernel for Whitespaces {
unsafe fn segment(t: *const c_void, n: usize, o: *mut usize, l: *mut usize, c: usize, u: *mut usize) -> usize {
sz_utf8_whitespaces(t, n, o, l, c, u)
}
}
pub struct Delimiters;
impl SegmenterKernel for Delimiters {
unsafe fn segment(t: *const c_void, n: usize, o: *mut usize, l: *mut usize, c: usize, u: *mut usize) -> usize {
sz_utf8_delimiters(t, n, o, l, c, u)
}
}
pub struct Wordbreaks;
impl SegmenterKernel for Wordbreaks {
unsafe fn segment(t: *const c_void, n: usize, o: *mut usize, l: *mut usize, c: usize, u: *mut usize) -> usize {
sz_utf8_wordbreaks(t, n, o, l, c, u)
}
}
pub struct Graphemes;
impl SegmenterKernel for Graphemes {
unsafe fn segment(t: *const c_void, n: usize, o: *mut usize, l: *mut usize, c: usize, u: *mut usize) -> usize {
sz_utf8_graphemes(t, n, o, l, c, u)
}
}
pub struct Sentences;
impl SegmenterKernel for Sentences {
unsafe fn segment(t: *const c_void, n: usize, o: *mut usize, l: *mut usize, c: usize, u: *mut usize) -> usize {
sz_utf8_sentences(t, n, o, l, c, u)
}
}
pub struct Linebreaks;
impl SegmenterKernel for Linebreaks {
unsafe fn segment(t: *const c_void, n: usize, o: *mut usize, l: *mut usize, c: usize, u: *mut usize) -> usize {
sz_utf8_linebreaks(t, n, o, l, c, u)
}
}
pub trait SplitParts {
const FIRST: usize;
const STRIDE: usize;
}
pub struct Between;
impl SplitParts for Between {
const FIRST: usize = 0;
const STRIDE: usize = 2;
}
pub struct Separators;
impl SplitParts for Separators {
const FIRST: usize = 1;
const STRIDE: usize = 2;
}
pub struct Both;
impl SplitParts for Both {
const FIRST: usize = 0;
const STRIDE: usize = 1;
}
pub trait EmptySegments {
const SKIP: bool;
}
pub struct KeepEmpty;
impl EmptySegments for KeepEmpty {
const SKIP: bool = false;
}
pub struct SkipEmpty;
impl EmptySegments for SkipEmpty {
const SKIP: bool = true;
}
pub trait Overlaps {
const OVERLAP: bool;
}
pub struct NonOverlapping;
impl Overlaps for NonOverlapping {
const OVERLAP: bool = false;
}
pub struct Overlapping;
impl Overlaps for Overlapping {
const OVERLAP: bool = true;
}
pub struct Utf8Split<
'a,
Kernel: SegmenterKernel,
Parts: SplitParts = Between,
Empty: EmptySegments = KeepEmpty,
const STEPS: usize = ITERATORS_DEFAULT_STEPS,
> {
text: &'a [u8],
suffix: usize, starts: [usize; STEPS], lengths: [usize; STEPS], separators: usize, region: usize, spans: usize, index: usize, advance: usize, _markers: PhantomData<(Kernel, Parts, Empty)>,
}
impl<'a, Kernel: SegmenterKernel, Parts: SplitParts, Empty: EmptySegments>
Utf8Split<'a, Kernel, Parts, Empty, ITERATORS_DEFAULT_STEPS>
{
pub fn new(text: &'a [u8]) -> Self {
Self::with_steps(text)
}
}
impl<'a, Kernel: SegmenterKernel, Parts: SplitParts, Empty: EmptySegments, const STEPS: usize>
Utf8Split<'a, Kernel, Parts, Empty, STEPS>
{
pub fn with_steps(text: &'a [u8]) -> Self {
let mut splits = Self {
text,
suffix: 0,
starts: [0; STEPS],
lengths: [0; STEPS],
separators: 0,
region: 0,
spans: 0,
index: 0,
advance: 0,
_markers: PhantomData,
};
splits.refill();
splits.settle();
splits
}
#[inline]
fn bound(&self, boundary: usize) -> usize {
if boundary == 0 {
0
} else if boundary > 2 * self.separators {
self.region } else if boundary & 1 == 1 {
self.starts[(boundary - 1) / 2]
} else {
let separator = boundary / 2 - 1;
self.starts[separator] + self.lengths[separator]
}
}
fn refill(&mut self) {
self.region = self.text.len() - self.suffix;
let mut consumed = 0usize;
self.separators = unsafe {
Kernel::segment(
self.text[self.suffix..].as_ptr() as *const c_void,
self.region,
self.starts.as_mut_ptr(),
self.lengths.as_mut_ptr(),
STEPS,
&mut consumed,
)
};
debug_assert!(
self.separators <= STEPS,
"segmenter reported more spans than the capacity STEPS"
);
debug_assert!(consumed <= self.region, "segmenter consumed past the region end");
debug_assert!(
consumed > 0 || self.region == 0,
"segmenter made no progress (the iterator would loop forever)"
);
debug_assert!(
(0..self.separators).all(|s| self.starts[s] + self.lengths[s] <= self.region
&& (s == 0 || self.starts[s] >= self.starts[s - 1] + self.lengths[s - 1])),
"separator spans run past the region, overlap, or are out of order"
);
debug_assert!(
self.separators < STEPS || consumed == self.starts[self.separators - 1] + self.lengths[self.separators - 1],
"segmenter resumed past the end of its last emitted separator"
);
let eof = consumed == self.region;
self.spans = 2 * self.separators + if eof { 1 } else { 0 };
self.advance = if eof { self.region + 1 } else { consumed };
self.index = Parts::FIRST;
}
fn settle(&mut self) {
loop {
if Empty::SKIP {
while self.index < self.spans && self.bound(self.index + 1) == self.bound(self.index) {
self.index += Parts::STRIDE;
}
}
if self.index < self.spans || self.spans == 0 {
return;
}
self.suffix += self.advance;
if self.suffix > self.text.len() {
self.spans = 0;
return;
}
self.refill();
}
}
}
impl<'a, Kernel: SegmenterKernel, Parts: SplitParts, const STEPS: usize>
Utf8Split<'a, Kernel, Parts, KeepEmpty, STEPS>
{
pub fn skip_empty(self) -> Utf8Split<'a, Kernel, Parts, SkipEmpty, STEPS> {
Utf8Split::with_steps(self.text)
}
}
impl<'a, Kernel: SegmenterKernel, Empty: EmptySegments, const STEPS: usize>
Utf8Split<'a, Kernel, Between, Empty, STEPS>
{
pub fn with_separators(self) -> Utf8Split<'a, Kernel, Both, Empty, STEPS> {
Utf8Split::with_steps(self.text)
}
}
impl<'a, Kernel: SegmenterKernel, Parts: SplitParts, Empty: EmptySegments, const STEPS: usize> Iterator
for Utf8Split<'a, Kernel, Parts, Empty, STEPS>
{
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.spans == 0 {
return None;
}
let begin = self.suffix + self.bound(self.index);
let end = self.suffix + self.bound(self.index + 1);
self.index += Parts::STRIDE;
self.settle();
Some(&self.text[begin..end])
}
}
pub type Utf8SplitNewlines<'a, const STEPS: usize = ITERATORS_DEFAULT_STEPS> =
Utf8Split<'a, Newlines, Between, KeepEmpty, STEPS>;
pub type Utf8Newlines<'a, const STEPS: usize = ITERATORS_DEFAULT_STEPS> =
Utf8Split<'a, Newlines, Separators, KeepEmpty, STEPS>;
pub type Utf8SplitWhitespaces<'a, const STEPS: usize = ITERATORS_DEFAULT_STEPS> =
Utf8Split<'a, Whitespaces, Between, KeepEmpty, STEPS>;
pub type Utf8Whitespaces<'a, const STEPS: usize = ITERATORS_DEFAULT_STEPS> =
Utf8Split<'a, Whitespaces, Separators, KeepEmpty, STEPS>;
pub type Utf8SplitDelimiters<'a, const STEPS: usize = ITERATORS_DEFAULT_STEPS> =
Utf8Split<'a, Delimiters, Between, KeepEmpty, STEPS>;
pub type Utf8Delimiters<'a, const STEPS: usize = ITERATORS_DEFAULT_STEPS> =
Utf8Split<'a, Delimiters, Separators, KeepEmpty, STEPS>;
pub const ITERATORS_DEFAULT_STEPS: usize = 64;
pub struct Utf8Segments<'a, Kernel: SegmenterKernel, const STEPS: usize = ITERATORS_DEFAULT_STEPS> {
text: &'a [u8],
suffix: usize, starts: [usize; STEPS], lengths: [usize; STEPS], count: usize, index: usize, _kernel: PhantomData<Kernel>, }
impl<'a, Kernel: SegmenterKernel> Utf8Segments<'a, Kernel, ITERATORS_DEFAULT_STEPS> {
pub fn new(text: &'a [u8]) -> Self {
Self::with_steps(text)
}
}
impl<'a, Kernel: SegmenterKernel, const STEPS: usize> Utf8Segments<'a, Kernel, STEPS> {
pub fn with_steps(text: &'a [u8]) -> Self {
let mut splits = Self {
text,
suffix: 0,
starts: [0; STEPS],
lengths: [0; STEPS],
count: 0,
index: 0,
_kernel: PhantomData,
};
splits.fill();
splits
}
fn fill(&mut self) {
let mut consumed = 0usize;
self.count = unsafe {
Kernel::segment(
self.text[self.suffix..].as_ptr() as *const c_void,
self.text.len() - self.suffix,
self.starts.as_mut_ptr(),
self.lengths.as_mut_ptr(),
STEPS,
&mut consumed,
)
};
self.index = 0;
}
}
impl<'a, Kernel: SegmenterKernel, const STEPS: usize> Iterator for Utf8Segments<'a, Kernel, STEPS> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.index == self.count {
if self.count == 0 {
return None; }
self.suffix += self.starts[self.count - 1] + self.lengths[self.count - 1];
self.fill();
if self.count == 0 {
return None;
}
}
let begin = self.suffix + self.starts[self.index];
let end = begin + self.lengths[self.index];
self.index += 1;
Some(&self.text[begin..end])
}
}
pub type Utf8Wordbreaks<'a, const STEPS: usize = ITERATORS_DEFAULT_STEPS> = Utf8Segments<'a, Wordbreaks, STEPS>;
pub type Utf8Graphemes<'a, const STEPS: usize = ITERATORS_DEFAULT_STEPS> = Utf8Segments<'a, Graphemes, STEPS>;
pub type Utf8Sentences<'a, const STEPS: usize = ITERATORS_DEFAULT_STEPS> = Utf8Segments<'a, Sentences, STEPS>;
pub type Utf8Linebreaks<'a, const STEPS: usize = ITERATORS_DEFAULT_STEPS> = Utf8Segments<'a, Linebreaks, STEPS>;
pub struct Utf8UncasedMatches<'a, O: Overlaps = NonOverlapping> {
haystack: &'a [u8],
needle: &'a [u8],
metadata: Utf8UncasedNeedleMetadata,
position: usize,
_overlaps: PhantomData<O>,
}
impl<'a> Utf8UncasedMatches<'a, NonOverlapping> {
pub fn new(haystack: &'a [u8], needle: &'a [u8]) -> Self {
Self {
haystack,
needle,
metadata: Utf8UncasedNeedleMetadata::default(),
position: 0,
_overlaps: PhantomData,
}
}
pub fn overlapping(self) -> Utf8UncasedMatches<'a, Overlapping> {
Utf8UncasedMatches {
haystack: self.haystack,
needle: self.needle,
metadata: self.metadata,
position: self.position,
_overlaps: PhantomData,
}
}
}
impl<'a, O: Overlaps> Iterator for Utf8UncasedMatches<'a, O> {
type Item = IndexSpan;
fn next(&mut self) -> Option<Self::Item> {
if self.position > self.haystack.len() {
return None;
}
let remaining = &self.haystack[self.position..];
let mut matched_length: usize = 0;
let result = unsafe {
sz_utf8_uncased_search(
remaining.as_ptr() as *const c_void,
remaining.len(),
self.needle.as_ptr() as *const c_void,
self.needle.len(),
&mut self.metadata,
&mut matched_length,
)
};
if result.is_null() {
self.position = self.haystack.len() + 1;
None
} else {
let offset_in_remaining = unsafe { result.offset_from(remaining.as_ptr() as *const c_void) } as usize;
let absolute_offset = self.position + offset_in_remaining;
if O::OVERLAP {
self.position = absolute_offset + 1;
} else {
self.position = absolute_offset + matched_length.max(1);
}
Some(IndexSpan::new(absolute_offset, matched_length))
}
}
}
pub trait StringZillableUnary {
fn sz_bytesum(&self) -> u64;
fn sz_hash(&self) -> u64;
fn sz_utf8_runes(&self) -> Utf8View<'_>;
fn sz_utf8_split_newlines(&self) -> Utf8SplitNewlines<'_>;
fn sz_utf8_newlines(&self) -> Utf8Newlines<'_>;
fn sz_utf8_split_whitespaces(&self) -> Utf8SplitWhitespaces<'_>;
fn sz_utf8_whitespaces(&self) -> Utf8Whitespaces<'_>;
fn sz_utf8_split_delimiters(&self) -> Utf8SplitDelimiters<'_>;
fn sz_utf8_delimiters(&self) -> Utf8Delimiters<'_>;
fn sz_utf8_wordbreaks(&self) -> Utf8Wordbreaks<'_>;
fn sz_utf8_graphemes(&self) -> Utf8Graphemes<'_>;
fn sz_utf8_sentences(&self) -> Utf8Sentences<'_>;
fn sz_utf8_linebreaks(&self) -> Utf8Linebreaks<'_>;
}
pub trait StringZillableBinary<'a, Needle>
where
Needle: AsRef<[u8]> + 'a,
{
fn sz_find(&self, needle: Needle) -> Option<usize>;
fn sz_rfind(&self, needle: Needle) -> Option<usize>;
fn sz_find_byte_from(&self, needles: Needle) -> Option<usize>;
fn sz_rfind_byte_from(&self, needles: Needle) -> Option<usize>;
fn sz_find_byte_not_from(&self, needles: Needle) -> Option<usize>;
fn sz_rfind_byte_not_from(&self, needles: Needle) -> Option<usize>;
fn sz_matches(&'a self, needle: &'a Needle) -> FindMatches<'a>;
fn sz_rmatches(&'a self, needle: &'a Needle) -> RFindMatches<'a>;
fn sz_splits(&'a self, needle: &'a Needle) -> FindSplits<'a>;
fn sz_rsplits(&'a self, needle: &'a Needle) -> RFindSplits<'a>;
fn sz_find_first_of(&'a self, needles: &'a Needle) -> FindMatches<'a>;
fn sz_find_last_of(&'a self, needles: &'a Needle) -> RFindMatches<'a>;
fn sz_find_first_not_of(&'a self, needles: &'a Needle) -> FindMatches<'a>;
fn sz_find_last_not_of(&'a self, needles: &'a Needle) -> RFindMatches<'a>;
}
impl<Source> StringZillableUnary for Source
where
Source: AsRef<[u8]> + ?Sized,
{
fn sz_bytesum(&self) -> u64 {
bytesum(self)
}
fn sz_hash(&self) -> u64 {
hash(self)
}
fn sz_utf8_runes(&self) -> Utf8View<'_> {
Utf8View::new(self.as_ref())
}
fn sz_utf8_split_newlines(&self) -> Utf8SplitNewlines<'_> {
Utf8SplitNewlines::new(self.as_ref())
}
fn sz_utf8_newlines(&self) -> Utf8Newlines<'_> {
Utf8Newlines::new(self.as_ref())
}
fn sz_utf8_split_whitespaces(&self) -> Utf8SplitWhitespaces<'_> {
Utf8SplitWhitespaces::new(self.as_ref())
}
fn sz_utf8_whitespaces(&self) -> Utf8Whitespaces<'_> {
Utf8Whitespaces::new(self.as_ref())
}
fn sz_utf8_split_delimiters(&self) -> Utf8SplitDelimiters<'_> {
Utf8SplitDelimiters::new(self.as_ref())
}
fn sz_utf8_delimiters(&self) -> Utf8Delimiters<'_> {
Utf8Delimiters::new(self.as_ref())
}
fn sz_utf8_wordbreaks(&self) -> Utf8Wordbreaks<'_> {
Utf8Wordbreaks::new(self.as_ref())
}
fn sz_utf8_graphemes(&self) -> Utf8Graphemes<'_> {
Utf8Graphemes::new(self.as_ref())
}
fn sz_utf8_sentences(&self) -> Utf8Sentences<'_> {
Utf8Sentences::new(self.as_ref())
}
fn sz_utf8_linebreaks(&self) -> Utf8Linebreaks<'_> {
Utf8Linebreaks::new(self.as_ref())
}
}
impl<'a, Source, Needle> StringZillableBinary<'a, Needle> for Source
where
Source: AsRef<[u8]> + ?Sized,
Needle: AsRef<[u8]> + 'a,
{
fn sz_find(&self, needle: Needle) -> Option<usize> {
find(self, needle)
}
fn sz_rfind(&self, needle: Needle) -> Option<usize> {
rfind(self, needle)
}
fn sz_find_byte_from(&self, needles: Needle) -> Option<usize> {
find_byte_from(self, needles)
}
fn sz_rfind_byte_from(&self, needles: Needle) -> Option<usize> {
rfind_byte_from(self, needles)
}
fn sz_find_byte_not_from(&self, needles: Needle) -> Option<usize> {
find_byte_not_from(self, needles)
}
fn sz_rfind_byte_not_from(&self, needles: Needle) -> Option<usize> {
rfind_byte_not_from(self, needles)
}
fn sz_matches(&'a self, needle: &'a Needle) -> FindMatches<'a> {
FindMatches::new(self.as_ref(), MatcherType::Find(needle.as_ref()))
}
fn sz_rmatches(&'a self, needle: &'a Needle) -> RFindMatches<'a> {
RFindMatches::new(self.as_ref(), MatcherType::RFind(needle.as_ref()))
}
fn sz_splits(&'a self, needle: &'a Needle) -> FindSplits<'a> {
FindSplits::new(self.as_ref(), MatcherType::Find(needle.as_ref()))
}
fn sz_rsplits(&'a self, needle: &'a Needle) -> RFindSplits<'a> {
RFindSplits::new(self.as_ref(), MatcherType::RFind(needle.as_ref()))
}
fn sz_find_first_of(&'a self, needles: &'a Needle) -> FindMatches<'a> {
FindMatches::new(self.as_ref(), MatcherType::FindFirstOf(needles.as_ref()))
}
fn sz_find_last_of(&'a self, needles: &'a Needle) -> RFindMatches<'a> {
RFindMatches::new(self.as_ref(), MatcherType::FindLastOf(needles.as_ref()))
}
fn sz_find_first_not_of(&'a self, needles: &'a Needle) -> FindMatches<'a> {
FindMatches::new(self.as_ref(), MatcherType::FindFirstNotOf(needles.as_ref()))
}
fn sz_find_last_not_of(&'a self, needles: &'a Needle) -> RFindMatches<'a> {
RFindMatches::new(self.as_ref(), MatcherType::FindLastNotOf(needles.as_ref()))
}
}
#[cfg(test)]
mod tests {
const PROSE_HOTEL_REVIEW: &str = concat!(
"Last spring we strolled down M\u{fc}nchner Stra\u{df}e; the cafe\u{301} cortado cost 3,50\u{a0}",
"\u{20ac} and was unreal. Dr. Vogel, our guide, swore it's the city's finest. Worth the detour?! ",
"Absolutely \u{2014} and \u{6771}\u{4eac}\u{30bf}\u{30ef}\u{30fc} the next week, all 333\u{a0}m o",
"f it, was breathtaking at dusk\u{2026}"
);
const PROSE_PRIDE_CAPTION: &str = concat!(
"Best Pride yet \u{1f3f3}\u{fe0f}\u{200d}\u{1f308} \u{2014} the whole crew showed up. Even my par",
"ents \u{1f468}\u{200d}\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466} and grandma \u{1f44d}\u{1f3fd}",
" came through! We met at booth 5\u{fe0f}\u{20e3}, then waved every flag we packed \u{1f1fa}",
"\u{1f1f8}\u{1f1ef}\u{1f1f5}\u{1f1eb}. Texting \u{260e}\u{fe0e} over calling \u{2708}\u{fe0f} all",
" day; 10/10, would march again."
);
const PROSE_CONCERT_POST: &str = concat!(
"\u{c624}\u{b298} \u{cf58}\u{c11c}\u{d2b8}, \u{c9c4}\u{c9dc} \u{bbf8}\u{cce4}\u{b2e4}!! \u{1112}",
"\u{1161}\u{11ab}\u{ad6d} \u{d32c}\u{b4e4}\u{c774} \u{b2e4} \u{baa8}\u{c600}\u{ace0}, the staff b",
"owed and said \u{c548}\u{b155}\u{d788} \u{ac00}\u{c138}\u{c694}. Setlist was pure \u{30cf}",
"\u{30fc}\u{30c9}\u{30b3}\u{30a2}; \u{4eca}\u{65e5}\u{306f}\u{6700}\u{9ad8}\u{3060}\u{3063}",
"\u{305f}\u{3002} We screamed \u{c0ac}\u{b791}\u{d574} till 11 p.m. sharp."
);
const PROSE_DEVANAGARI_TIP: &str = concat!(
"Quick Devanagari tip: \u{915}\u{94d}\u{937} is one cluster (\u{915} + \u{94d} + \u{937}), not th",
"ree. Force the half-form with ZWJ \u{2014} \u{915}\u{94d}\u{200d}\u{937} \u{2014} or split it wi",
"th ZWNJ \u{2014} \u{915}\u{94d}\u{200c}\u{937}. The same logic hits \u{915}\u{94d}\u{937}\u{924}",
"\u{94d}\u{930}\u{93f}\u{92f} and spacing vowel signs like \u{915}\u{940}. Renderers disagree, so",
" test (\u{bd} the bugs are font bugs) before you ship!"
);
const PROSE_SCIENCE_ABSTRACT: &str = concat!(
"The \u{fb01}lm grew at 300\u{a0}\u{212a} on a 5\u{a0}\u{212b} buffer (\u{2248} 2\u{b2} monolayer",
"s). Section \u{216b} covers the \u{ff21}-phase; see Fig. 2 for the \u{3a3}-band dispersion. Resi",
"stivity scaled as T\u{b2}, vanishing at the 4.2\u{a0}\u{212a} transition. Full dataset: doi:10.1",
"000\u{2060}/\u{200b}xyz (mirror in Box \u{2461})."
);
const PROSE_NEWS_LEDE: &str = concat!(
"The U.S.A. wasn't ready, analysts said. \u{201c}We lost 1,000 jobs,\u{201d} the mayor warned. ",
"\u{201c}Recovery starts now.\u{201d} Filings spiked 2024/06\u{2013}2024/09, topping $1,000 per c",
"laim. Will it hold?! No one knows for sure."
);
#[allow(dead_code)] const PROSE_LANGUAGE_LESSON: &str = concat!(
"Greek lesson: \u{39f}\u{394}\u{39f}\u{3a3} becomes \u{3bf}\u{3b4}\u{3cc}\u{3c2} when lowercased,",
" ending in a final \u{3c2}. Russian's easy too \u{2014} \u{41c}\u{41e}\u{421}\u{41a}\u{412}",
"\u{410} \u{2194} \u{43c}\u{43e}\u{441}\u{43a}\u{432}\u{430}, no drama. Croatian has the digraph ",
"\u{1c4}: titlecase \u{1c5}, lowercase \u{1c6}. Quiz \u{2014} does \u{201c}stra\u{df}e\u{201d} ma",
"tch STRASSE? Yes, once you fold."
);
const PROSE_RTL_SCRIPTS: &str = concat!(
"Hebrew acronyms take gershayim: \u{5e6}\u{5d4}\u{5f4}\u{5dc} and \u{5d0}\u{5e8}\u{5d4}\u{5f4}",
"\u{5d1} aren't typos. Arabic flows right-to-left too \u{2014} \u{645}\u{631}\u{62d}\u{628}",
"\u{627} \u{628}\u{627}\u{644}\u{639}\u{627}\u{644}\u{645} \u{2014} and finance text can carry th",
"e number sign \u{600}\u{664}. Niqqud stacks marks: \u{5e9}\u{5c1}\u{5b8}\u{5dc}\u{5d5}\u{5b9}",
"\u{5dd} must reorder under NFC. Malayalam even has a true prepend, the dot-reph \u{d4e}\u{d15}."
);
const PROSE_MICRO_APOSTROPHE: &str = "it\u{2019}s worth it";
const PROSE_MICRO_PREPEND: &str = "\u{600}\u{664} \u{d4e}\u{d15}";
const PROSE_MICRO_HARDBREAKS: &str = "A.\u{d}\u{a}B.\u{2028}C.";
const CAFE_NFC: &str = "caf\u{00E9}";
const CAFE_NFD: &str = "cafe\u{0301}";
fn fold_codepoint(codepoint: char) -> ([u8; 16], usize) {
let mut source_buffer = [0u8; 4];
let source = codepoint.encode_utf8(&mut source_buffer);
let mut folded = [0u8; 16];
let folded_length = sz::utf8_uncased_fold(source.as_bytes(), &mut folded[..]);
debug_assert!(folded_length <= folded.len(), "fold expansion exceeded buffer");
(folded, folded_length)
}
fn reference_uncased_find(haystack: &str, needle: &str) -> Option<(usize, usize)> {
const CAPACITY: usize = 512;
let mut haystack_folded = [0u8; CAPACITY];
let mut source_starts = [0usize; CAPACITY];
let mut source_ends = [0usize; CAPACITY];
let mut haystack_folded_length = 0usize;
let mut original_offset = 0usize;
for codepoint in haystack.chars() {
let codepoint_length = codepoint.len_utf8();
let codepoint_start = original_offset;
let codepoint_end = original_offset + codepoint_length;
let (folded, folded_length) = fold_codepoint(codepoint);
for byte_index in 0..folded_length {
debug_assert!(haystack_folded_length < CAPACITY, "haystack fold overflow");
haystack_folded[haystack_folded_length] = folded[byte_index];
source_starts[haystack_folded_length] = codepoint_start;
source_ends[haystack_folded_length] = codepoint_end;
haystack_folded_length += 1;
}
original_offset = codepoint_end;
}
let mut needle_folded = [0u8; CAPACITY];
let mut needle_folded_length = 0usize;
let mut needle_buffer = [0u8; 4];
for codepoint in needle.chars() {
let source = codepoint.encode_utf8(&mut needle_buffer);
let mut folded = [0u8; 16];
let folded_length = sz::utf8_uncased_fold(source.as_bytes(), &mut folded[..]);
for byte_index in 0..folded_length {
debug_assert!(needle_folded_length < CAPACITY, "needle fold overflow");
needle_folded[needle_folded_length] = folded[byte_index];
needle_folded_length += 1;
}
}
let haystack_fold = &haystack_folded[..haystack_folded_length];
let needle_fold = &needle_folded[..needle_folded_length];
if needle_fold.is_empty() {
return Some((0, 0));
}
if needle_fold.len() > haystack_fold.len() {
return None;
}
for run_start in 0..=(haystack_fold.len() - needle_fold.len()) {
let run_end = run_start + needle_fold.len();
if &haystack_fold[run_start..run_end] == needle_fold {
let offset = source_starts[run_start];
let length = source_ends[run_end - 1] - offset;
return Some((offset, length));
}
}
None
}
#[test]
fn utf8_prose_sentence_counts() {
assert_eq!(PROSE_HOTEL_REVIEW.as_bytes().sz_utf8_sentences().count(), 5);
assert_eq!(PROSE_CONCERT_POST.as_bytes().sz_utf8_sentences().count(), 4);
assert_eq!(PROSE_NEWS_LEDE.as_bytes().sz_utf8_sentences().count(), 6);
assert_eq!(PROSE_MICRO_HARDBREAKS.as_bytes().sz_utf8_sentences().count(), 3);
}
#[test]
fn utf8_prose_wordbreak_counts() {
assert_eq!(PROSE_HOTEL_REVIEW.as_bytes().sz_utf8_wordbreaks().count(), 100);
assert_eq!(PROSE_NEWS_LEDE.as_bytes().sz_utf8_wordbreaks().count(), 83);
assert_eq!(PROSE_CONCERT_POST.as_bytes().sz_utf8_wordbreaks().count(), 69);
assert_eq!(PROSE_RTL_SCRIPTS.as_bytes().sz_utf8_wordbreaks().count(), 98);
assert_eq!(PROSE_MICRO_APOSTROPHE.as_bytes().sz_utf8_wordbreaks().count(), 5);
}
#[test]
fn utf8_prose_grapheme_counts() {
assert_eq!(PROSE_PRIDE_CAPTION.as_bytes().sz_utf8_graphemes().count(), 206);
assert_eq!(PROSE_DEVANAGARI_TIP.as_bytes().sz_utf8_graphemes().count(), 252);
assert_eq!(PROSE_CONCERT_POST.as_bytes().sz_utf8_graphemes().count(), 134);
assert_eq!(PROSE_RTL_SCRIPTS.as_bytes().sz_utf8_graphemes().count(), 256);
assert_eq!(PROSE_MICRO_PREPEND.as_bytes().sz_utf8_graphemes().count(), 3);
assert_eq!(PROSE_PRIDE_CAPTION.as_bytes().sz_utf8_runes().iter().count(), 222);
assert!(
PROSE_PRIDE_CAPTION.as_bytes().sz_utf8_runes().iter().count()
> PROSE_PRIDE_CAPTION.as_bytes().sz_utf8_graphemes().count()
);
}
#[test]
fn utf8_prose_linebreak_counts() {
assert_eq!(PROSE_HOTEL_REVIEW.as_bytes().sz_utf8_linebreaks().count(), 45);
assert_eq!(PROSE_SCIENCE_ABSTRACT.as_bytes().sz_utf8_linebreaks().count(), 43);
assert_eq!(PROSE_NEWS_LEDE.as_bytes().sz_utf8_linebreaks().count(), 32);
}
extern crate alloc;
use alloc::borrow::Cow;
use alloc::format;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::hash::Hasher as _;
#[cfg(feature = "std")]
use std::collections::{HashMap, HashSet};
use super::*;
use crate::sz;
#[test]
fn metadata() {
assert_eq!(sz::dynamic_dispatch(), cfg!(feature = "dynamic-dispatch"));
assert!(sz::capabilities().as_str().len() > 0);
}
#[test]
fn bytesum() {
assert_eq!(sz::bytesum("hi"), 209u64);
}
#[test]
fn utf8_delimiters() {
let toks: Vec<&[u8]> = "Hi, world\u{2014}foo"
.as_bytes()
.sz_utf8_split_delimiters()
.skip_empty()
.collect();
assert_eq!(toks, vec![&b"Hi"[..], &b"world"[..], &b"foo"[..]]);
let kept: Vec<&[u8]> = "a,,b".as_bytes().sz_utf8_split_delimiters().collect();
assert_eq!(kept, vec![&b"a"[..], &b""[..], &b"b"[..]]);
}
#[test]
fn utf8_split_delimiters_sparse_batches() {
for run in [16usize, 31, 62, 63, 64, 100] {
let text = format!("a {} c", "b".repeat(run));
let expected: Vec<&[u8]> = vec![b"a", text.as_bytes()[2..2 + run].as_ref(), b"c"];
for tiny in [
Utf8SplitDelimiters::<1>::with_steps(text.as_bytes()).collect::<Vec<_>>(),
Utf8SplitDelimiters::<2>::with_steps(text.as_bytes()).collect::<Vec<_>>(),
text.as_bytes().sz_utf8_split_delimiters().collect::<Vec<_>>(),
] {
assert_eq!(tiny, expected, "run of {} undelimited bytes", run);
}
let both: Vec<&[u8]> = Utf8SplitDelimiters::<1>::with_steps(text.as_bytes())
.with_separators()
.collect();
assert_eq!(both.concat(), text.as_bytes());
}
}
#[test]
fn utf8_split_modes() {
let text = "a b c".as_bytes();
let between: Vec<&[u8]> = text.sz_utf8_split_whitespaces().collect();
assert_eq!(between, vec![&b"a"[..], &b"b"[..], &b""[..], &b"c"[..]]);
let seps: Vec<&[u8]> = text.sz_utf8_whitespaces().collect();
assert_eq!(seps, vec![&b" "[..], &b" "[..], &b" "[..]]);
let both: Vec<&[u8]> = text.sz_utf8_split_whitespaces().with_separators().collect();
assert_eq!(both.concat(), text);
for t in [" x ", "", "abc", "a\r\nb"] {
let rt: Vec<&[u8]> = t.as_bytes().sz_utf8_split_newlines().with_separators().collect();
assert_eq!(rt.concat(), t.as_bytes());
}
let empty: Vec<&[u8]> = "".as_bytes().sz_utf8_split_whitespaces().collect();
assert_eq!(empty, vec![&b""[..]]);
let many = "w ".repeat(50) + "end";
let between_small: Vec<&[u8]> = Utf8SplitWhitespaces::<2>::with_steps(many.as_bytes()).collect();
assert_eq!(
between_small,
many.as_bytes().sz_utf8_split_whitespaces().collect::<Vec<_>>()
);
let seps_small: Vec<&[u8]> = Utf8Whitespaces::<2>::with_steps(many.as_bytes()).collect();
assert_eq!(seps_small, many.as_bytes().sz_utf8_whitespaces().collect::<Vec<_>>());
let both_small: Vec<&[u8]> = Utf8SplitWhitespaces::<2>::with_steps(many.as_bytes())
.with_separators()
.collect();
assert_eq!(both_small.concat(), many.as_bytes()); assert_eq!(
both_small,
many.as_bytes()
.sz_utf8_split_whitespaces()
.with_separators()
.collect::<Vec<_>>()
);
let dropped: Vec<&[u8]> = "a b"
.as_bytes()
.sz_utf8_split_whitespaces()
.skip_empty()
.with_separators()
.collect();
assert!(dropped.iter().all(|s| !s.is_empty()));
let segs: Vec<&[u8]> = "Hello, world!".as_bytes().sz_utf8_wordbreaks().collect();
assert_eq!(segs.concat(), &b"Hello, world!"[..]);
assert_eq!(segs.len(), 5);
}
#[test]
fn hash() {
let hash_hello = sz::hash("Hello");
let hash_world = sz::hash("World");
assert_ne!(hash_hello, hash_world);
for seed in [0u64, 42, 123456789].iter() {
assert_eq!(
sz::Hasher::new(*seed).update("Hello".as_bytes()).digest(),
sz::hash_with_seed("Hello", *seed)
);
assert_eq!(
sz::Hasher::new(*seed)
.update("Hello".as_bytes())
.update("World".as_bytes())
.digest(),
sz::hash_with_seed("HelloWorld", *seed)
);
}
}
#[test]
fn streaming_hash() {
let mut hasher = sz::Hasher::new(123);
hasher.write(b"Hello, ");
hasher.write(b"world!");
let streamed = hasher.finish();
let mut hasher = sz::Hasher::new(123);
hasher.write(b"Hello, world!");
let expected = hasher.finish();
assert_eq!(streamed, expected);
}
#[test]
fn multiseed_hash() {
let seeds: Vec<u64> = (0..9u64)
.map(|i| i.wrapping_mul(0x9E3779B97F4A7C15).wrapping_add(7))
.collect();
let texts: [&[u8]; 5] = [
b"",
b"token",
b"sixteen_bytes!!!",
b"sixty four chars exactly here to fill one whole block boundary..",
b"a string definitely longer than sixty four bytes to hit the wide path here please",
];
for text in texts {
for k in 0..=seeds.len() {
let mut out = vec![0u64; k];
sz::hash_multiseed_into(text, &seeds[..k], &mut out);
for i in 0..k {
assert_eq!(
out[i],
sz::hash_with_seed(text, seeds[i]),
"len={} k={} i={}",
text.len(),
k,
i
);
}
}
}
}
#[test]
#[cfg(feature = "std")]
fn hashmap_with_sz() {
let mut map: HashMap<&str, i32, sz::BuildSzHasher> = HashMap::with_hasher(sz::BuildSzHasher::with_seed(0));
map.insert("a", 1);
map.insert("b", 2);
map.insert("c", 3);
assert_eq!(map.get("a"), Some(&1));
assert_eq!(map.get("b"), Some(&2));
assert_eq!(map.get("c"), Some(&3));
assert!(map.get("z").is_none());
}
#[test]
#[cfg(feature = "std")]
fn hashset_with_sz() {
let mut set: HashSet<&str, sz::BuildSzHasher> = HashSet::with_hasher(sz::BuildSzHasher::with_seed(42));
assert!(set.insert("alpha"));
assert!(set.insert("beta"));
assert!(set.contains("alpha"));
assert!(set.contains("beta"));
assert!(!set.contains("gamma"));
let len_before = set.len();
assert!(!set.insert("alpha"));
assert_eq!(set.len(), len_before);
}
#[test]
fn search() {
let my_string: String = String::from("Hello, world!");
let my_str: &str = my_string.as_str();
let my_cow_str: Cow<'_, str> = Cow::from(&my_string);
assert_eq!(sz::find("Hello, world!", "world"), Some(7));
assert_eq!(sz::rfind("Hello, world!", "world"), Some(7));
let world_string = String::from("world");
assert_eq!(my_string.sz_find(&world_string), Some(7));
assert_eq!(my_string.sz_rfind(&world_string), Some(7));
assert_eq!(my_string.sz_find_byte_from(&world_string), Some(2));
assert_eq!(my_string.sz_rfind_byte_from(&world_string), Some(11));
assert_eq!(my_string.sz_find_byte_not_from(&world_string), Some(0));
assert_eq!(my_string.sz_rfind_byte_not_from(&world_string), Some(12));
assert_eq!(my_str.sz_find("world"), Some(7));
assert_eq!(my_str.sz_rfind("world"), Some(7));
assert_eq!(my_str.sz_find_byte_from("world"), Some(2));
assert_eq!(my_str.sz_rfind_byte_from("world"), Some(11));
assert_eq!(my_str.sz_find_byte_not_from("world"), Some(0));
assert_eq!(my_str.sz_rfind_byte_not_from("world"), Some(12));
assert_eq!(my_cow_str.as_ref().sz_find("world"), Some(7));
assert_eq!(my_cow_str.as_ref().sz_rfind("world"), Some(7));
assert_eq!(my_cow_str.as_ref().sz_find_byte_from("world"), Some(2));
assert_eq!(my_cow_str.as_ref().sz_rfind_byte_from("world"), Some(11));
assert_eq!(my_cow_str.as_ref().sz_find_byte_not_from("world"), Some(0));
assert_eq!(my_cow_str.as_ref().sz_rfind_byte_not_from("world"), Some(12));
}
#[test]
fn empty_needle_matches_std() {
assert_eq!(sz::find("abc", ""), Some(0));
assert_eq!("abc".find(""), Some(0));
assert_eq!(sz::rfind("abc", ""), Some(3));
assert_eq!("abc".rfind(""), Some(3));
assert!(sz::contains("abc", ""));
assert!("abc".contains(""));
assert_eq!(sz::find("", ""), Some(0));
assert_eq!("".find(""), Some(0));
assert_eq!(sz::rfind("", ""), Some(0));
assert_eq!("".rfind(""), Some(0));
assert!(sz::contains("", ""));
assert!("".contains(""));
assert_eq!(sz::find("abc", "b"), Some(1));
assert_eq!(sz::rfind("abc", "b"), Some(1));
assert!(sz::contains("abc", "b"));
assert!(!sz::contains("abc", "z"));
}
#[test]
fn fill_random() {
let mut first_buffer: Vec<u8> = vec![0; 10]; let mut second_buffer: Vec<u8> = vec![1; 10]; sz::fill_random(&mut first_buffer, 42);
sz::fill_random(&mut second_buffer, 42);
assert_eq!(first_buffer, second_buffer);
}
#[test]
fn iter_matches_forward() {
let haystack = b"hello world hello universe";
let needle = b"hello";
let matches: Vec<_> = haystack.sz_matches(needle).collect();
assert_eq!(matches, vec![b"hello", b"hello"]);
}
#[test]
fn iter_matches_reverse() {
let haystack = b"hello world hello universe";
let needle = b"hello";
let matches: Vec<_> = haystack.sz_rmatches(needle).collect();
assert_eq!(matches, vec![b"hello", b"hello"]);
}
#[test]
fn iter_splits_forward() {
let haystack = b"alpha,beta;gamma";
let needle = b",";
let splits: Vec<_> = haystack.sz_splits(needle).collect();
assert_eq!(splits, vec![&b"alpha"[..], &b"beta;gamma"[..]]);
}
#[test]
fn iter_splits_reverse() {
let haystack = b"alpha,beta;gamma";
let needle = b";";
let splits: Vec<_> = haystack.sz_rsplits(needle).collect();
assert_eq!(splits, vec![&b"gamma"[..], &b"alpha,beta"[..]]);
}
#[test]
fn iter_splits_with_empty_parts() {
let haystack = b"a,,b,";
let needle = b",";
let splits: Vec<_> = haystack.sz_splits(needle).collect();
assert_eq!(splits, vec![b"a", &b""[..], b"b", &b""[..]]);
}
#[test]
fn iter_splits_empty_haystack_yields_one_empty_segment() {
let matcher = MatcherType::Find(b",");
let splits: Vec<_> = FindSplits::new(b"", matcher).collect();
assert_eq!(splits, vec![&b""[..]]);
}
#[test]
fn iter_matches_forward_empty_needle_matches_std() {
let matches: Vec<_> = FindMatches::new(b"abc", MatcherType::Find(b"")).collect();
assert_eq!(matches, vec![&b""[..]; 4]);
assert_eq!("abc".matches("").count(), 4);
}
#[test]
fn iter_matches_reverse_empty_needle() {
let matches: Vec<_> = RFindMatches::new(b"abc", MatcherType::RFind(b"")).collect();
assert_eq!(matches, vec![&b""[..]; 4]);
}
#[test]
fn iter_splits_forward_empty_needle() {
let splits: Vec<_> = FindSplits::new(b"abc", MatcherType::Find(b"")).collect();
assert_eq!(splits, vec![&b"abc"[..]]);
}
#[test]
fn iter_splits_reverse_empty_needle() {
let splits: Vec<_> = RFindSplits::new(b"abc", MatcherType::RFind(b"")).collect();
assert_eq!(splits, vec![&b"abc"[..]]);
}
#[test]
fn utf8_runes_match_std_chars() {
let long_mixed = "Hello, \u{43C}\u{438}\u{440}! \u{4E16}\u{754C} \u{1F30D}\u{1F680} \u{627}\u{644}".repeat(50);
let samples = [
"",
"A",
"Hello\u{1F30D}",
"\u{3A9}\u{3BC}\u{3AD}\u{3B3}\u{3B1}",
long_mixed.as_str(),
];
for text in samples {
let expected: Vec<char> = text.chars().collect();
let via_view: Vec<char> = sz::Utf8View::new(text.as_bytes()).iter().collect();
assert_eq!(
via_view, expected,
"rune iteration diverged from std::chars for {:?}",
text
);
let via_trait: Vec<char> = text.as_bytes().sz_utf8_runes().iter().collect();
assert_eq!(via_trait, expected);
}
}
#[test]
fn utf8_runes_with_steps_match_default() {
let text = "Hello, \u{43C}\u{438}\u{440}! \u{4E16}\u{754C} \u{1F30D} \u{627}\u{644}".repeat(10);
let expected: Vec<char> = text.chars().collect();
let tiny: Vec<char> = sz::Utf8Runes::<1>::with_steps(text.as_bytes()).collect();
let wide: Vec<char> = sz::Utf8Runes::<256>::with_steps(text.as_bytes()).collect();
assert_eq!(tiny, expected);
assert_eq!(wide, expected);
}
#[test]
fn utf8_decode_replaces_ill_formed() {
let ill_formed: [&[u8]; 4] = [b"\x80", b"\xC0\x80", b"\xED\xA0\x80", b"a\xFFb"];
for bytes in ill_formed {
let mut runes = [0u32; 16];
let mut offset = 0;
while offset < bytes.len() {
let (consumed, count) = sz::utf8_decode(&bytes[offset..], &mut runes);
for &rune in &runes[..count] {
assert!(
rune <= 0x10FFFF && !(0xD800..=0xDFFF).contains(&rune),
"non-scalar value 0x{:X}",
rune
);
}
if consumed == 0 {
break;
}
offset += consumed;
}
}
let mut runes = [0u32; 8];
let (_, count) = sz::utf8_decode(b"a\xFFb", &mut runes);
assert_eq!(&runes[..count], &['a' as u32, 0xFFFD, 'b' as u32]);
}
#[test]
fn utf8_runes_finalize_truncated_tail() {
let truncated = b"hi\xF0\x9F\x98"; let runes: Vec<char> = sz::Utf8View::new(truncated).iter().collect();
assert_eq!(runes, vec!['h', 'i', '\u{FFFD}']);
}
#[test]
fn iter_splits_forward_skip_empty() {
let haystack = b"a,,b,";
let needle = b",";
let kept: Vec<_> = haystack.sz_splits(needle).collect();
assert_eq!(kept, vec![b"a", &b""[..], b"b", &b""[..]]);
let nonempty: Vec<_> = haystack.sz_splits(needle).skip_empty().collect();
assert_eq!(nonempty, vec![b"a", b"b"]);
}
#[test]
fn iter_splits_reverse_skip_empty() {
let haystack = b"a,,b,";
let needle = b",";
let kept: Vec<_> = haystack.sz_rsplits(needle).collect();
assert_eq!(kept, vec![&b""[..], b"b", &b""[..], b"a"]);
let nonempty: Vec<_> = haystack.sz_rsplits(needle).skip_empty().collect();
assert_eq!(nonempty, vec![b"b", b"a"]);
}
#[test]
fn iter_splits_byteset_skip_empty() {
let haystack = b",a;;b,";
let kept: Vec<_> = FindSplits::new(haystack, MatcherType::FindFirstOf(b",;")).collect();
assert_eq!(kept, vec![&b""[..], b"a", &b""[..], b"b", &b""[..]]);
let nonempty: Vec<_> = FindSplits::new(haystack, MatcherType::FindFirstOf(b",;"))
.skip_empty()
.collect();
assert_eq!(nonempty, vec![b"a", b"b"]);
}
#[test]
fn iter_matches_with_overlaps() {
let haystack = b"aaaa";
let needle = b"aa";
let non_overlapping: Vec<_> = haystack.sz_matches(needle).collect();
assert_eq!(non_overlapping, vec![b"aa", b"aa"]);
let matches: Vec<_> = haystack.sz_matches(needle).overlapping().collect();
assert_eq!(matches, vec![b"aa", b"aa", b"aa"]);
}
#[test]
fn iter_splits_with_utf8_haystack() {
let haystack = "こんにちは,世界".as_bytes();
let needle = b",";
let splits: Vec<_> = haystack.sz_splits(needle).collect();
assert_eq!(splits, vec!["こんにちは".as_bytes(), "世界".as_bytes()]);
}
#[test]
fn iter_find_first_of() {
let haystack = b"hello world";
let needles = b"or";
let matches: Vec<_> = haystack.sz_find_first_of(needles).collect();
assert_eq!(matches, vec![b"o", b"o", b"r"]);
}
#[test]
fn iter_find_last_of() {
let haystack = b"hello world";
let needles = b"or";
let matches: Vec<_> = haystack.sz_find_last_of(needles).collect();
assert_eq!(matches, vec![b"r", b"o", b"o"]);
}
#[test]
fn iter_find_first_not_of() {
let haystack = b"aabbbcccd";
let needles = b"ab";
let matches: Vec<_> = haystack.sz_find_first_not_of(needles).collect();
assert_eq!(matches, vec![b"c", b"c", b"c", b"d"]);
}
#[test]
fn iter_find_last_not_of() {
let haystack = b"aabbbcccd";
let needles = b"cd";
let matches: Vec<_> = haystack.sz_find_last_not_of(needles).collect();
assert_eq!(matches, vec![b"b", b"b", b"b", b"a", b"a"]);
}
#[test]
fn iter_find_first_of_empty_needles() {
let haystack = b"hello world";
let needles = b"";
let matches: Vec<_> = haystack.sz_find_first_of(needles).collect();
assert_eq!(matches, Vec::<&[u8]>::new());
}
#[test]
fn iter_find_last_of_empty_haystack() {
let haystack = b"";
let needles = b"abc";
let matches: Vec<_> = haystack.sz_find_last_of(needles).collect();
assert_eq!(matches, Vec::<&[u8]>::new());
}
#[test]
fn iter_find_first_not_of_all_matching() {
let haystack = b"aaabbbccc";
let needles = b"abc";
let matches: Vec<_> = haystack.sz_find_first_not_of(needles).collect();
assert_eq!(matches, Vec::<&[u8]>::new());
}
#[test]
fn iter_find_last_not_of_all_not_matching() {
let haystack = b"hello world";
let needles = b"xyz";
let matches: Vec<_> = haystack.sz_find_last_not_of(needles).collect();
assert_eq!(
matches,
vec![b"d", b"l", b"r", b"o", b"w", b" ", b"o", b"l", b"l", b"e", b"h"]
);
}
#[test]
fn iter_find_matches_overlapping() {
let haystack = b"aaaa";
let matcher = MatcherType::Find(b"aa");
let matches: Vec<_> = FindMatches::new(haystack, matcher).overlapping().collect();
assert_eq!(matches, vec![&b"aa"[..], &b"aa"[..], &b"aa"[..]]);
}
#[test]
fn iter_find_matches_non_overlapping() {
let haystack = b"aaaa";
let matcher = MatcherType::Find(b"aa");
let matches: Vec<_> = FindMatches::new(haystack, matcher).collect();
assert_eq!(matches, vec![&b"aa"[..], &b"aa"[..]]);
}
#[test]
fn iter_rfind_matches_overlapping() {
let haystack = b"aaaa";
let matcher = MatcherType::RFind(b"aa");
let matches: Vec<_> = RFindMatches::new(haystack, matcher).overlapping().collect();
assert_eq!(matches, vec![&b"aa"[..], &b"aa"[..], &b"aa"[..]]);
}
#[test]
fn iter_rfind_matches_non_overlapping() {
let haystack = b"aaaa";
let matcher = MatcherType::RFind(b"aa");
let matches: Vec<_> = RFindMatches::new(haystack, matcher).collect();
assert_eq!(matches, vec![&b"aa"[..], &b"aa"[..]]);
}
#[test]
fn const_apis() {
const SPAN: sz::IndexSpan = sz::IndexSpan::new(6, 5);
const WHITESPACE: sz::Byteset = sz::Byteset::from_bytes(b" \t\r\n");
const NEEDLE: sz::Utf8UncasedNeedle = sz::Utf8UncasedNeedle::new(b"hello");
const TOP_TWO_DESCENDING: sz::ArgsortOptions = sz::ArgsortOptions {
reverse: false,
uncased: false,
top: None,
}
.reversed()
.top(2);
assert_eq!(SPAN.extract(b"Hello World"), b"World");
assert_eq!(sz::find_byteset("ab cd", WHITESPACE), Some(2));
assert_eq!(sz::utf8_uncased_search(b"say HELLO now", &NEEDLE), Some((4, 5)));
let fruits = ["banana", "apple", "cherry"];
let mut order = [0; 3];
sz::argsort(&fruits, &mut order, TOP_TWO_DESCENDING).expect("argsort failed");
assert_eq!(fruits[order[0]], "cherry");
}
#[test]
fn argsort_default() {
let fruits = ["banana", "apple", "cherry"];
let mut order = [0; 3]; sz::argsort(&fruits, &mut order, Default::default()).expect("argsort failed");
let sorted_from_api: Vec<_> = order.iter().map(|&i| fruits[i]).collect();
let mut expected = fruits.to_vec();
expected.sort();
assert_eq!(sorted_from_api, expected);
}
#[test]
fn argsort_by_custom() {
#[derive(Debug)]
#[allow(dead_code)]
struct Person {
name: &'static str,
age: u32, }
let people = [
Person {
name: "Charlie",
age: 30,
},
Person { name: "Alice", age: 25 },
Person { name: "Bob", age: 40 },
];
let mut order = [0; 3];
sz::argsort_by(|i: usize| people[i].name.as_bytes(), &mut order, Default::default())
.expect("argsort_by failed");
let sorted_from_api: Vec<_> = order.iter().map(|&i| people[i].name).collect();
let mut expected: Vec<_> = people.iter().map(|p| p.name).collect();
expected.sort();
assert_eq!(sorted_from_api, expected);
}
#[test]
fn argsort_reverse_is_stable() {
let labels = ["beta", "alpha", "beta", "gamma"];
let mut order = [0; 4];
sz::argsort(&labels, &mut order, sz::ArgsortOptions::default().reversed()).expect("argsort failed");
let sorted: Vec<_> = order.iter().map(|&i| labels[i]).collect();
assert_eq!(sorted, vec!["gamma", "beta", "beta", "alpha"]);
let beta_positions: Vec<_> = order.iter().filter(|&&i| labels[i] == "beta").copied().collect();
assert_eq!(beta_positions, vec![0, 2]);
}
#[test]
fn argsort_top_k_prefix() {
let words = ["delta", "alpha", "echo", "bravo", "charlie"];
let mut order = [0; 5];
sz::argsort(&words, &mut order, sz::ArgsortOptions::default().top(2)).expect("argsort failed");
assert_eq!(words[order[0]], "alpha");
assert_eq!(words[order[1]], "bravo");
let mut seen = order.to_vec();
seen.sort();
assert_eq!(seen, vec![0, 1, 2, 3, 4]);
}
#[test]
fn argsort_uncased() {
let labels = ["Banana", "apple", "BANANA", "Apple"];
let mut order = [0; 4];
sz::argsort(&labels, &mut order, sz::ArgsortOptions::default().uncased()).expect("argsort failed");
let sorted: Vec<_> = order.iter().map(|&i| labels[i]).collect();
assert_eq!(sorted, vec!["apple", "Apple", "Banana", "BANANA"]);
}
#[test]
#[cfg(feature = "std")]
fn intersection_default() {
let set1 = ["banana", "apple", "cherry"];
let set2 = ["cherry", "orange", "pineapple", "banana"];
let mut out1 = [0; 3];
let mut out2 = [0; 3];
let n = sz::intersection(&set1, &set2, 0, &mut out1, &mut out2).expect("intersection failed");
assert!(n <= set1.len().min(set2.len()));
let common_from_api: HashSet<_> = out1[..n].iter().map(|&i| set1[i]).collect();
let expected: HashSet<_> = set1
.iter()
.cloned()
.collect::<HashSet<_>>()
.intersection(&set2.iter().cloned().collect())
.cloned()
.collect();
assert_eq!(common_from_api, expected);
}
#[test]
#[cfg(feature = "std")]
fn intersection_by_custom() {
#[derive(Debug)]
#[allow(dead_code)]
struct Person {
name: &'static str,
age: u32, }
let group1 = [
Person { name: "Alice", age: 25 },
Person { name: "Bob", age: 30 },
Person {
name: "Charlie",
age: 35,
},
];
let group2 = [
Person { name: "David", age: 40 },
Person {
name: "Charlie",
age: 50,
},
Person { name: "Alice", age: 60 },
];
let mut out1 = [0; 3];
let mut out2 = [0; 3];
let n = sz::intersection_by(
|i: sz::SortedIdx| group1[i].name.as_bytes(),
|j: sz::SortedIdx| group2[j].name.as_bytes(),
0,
&mut out1,
&mut out2,
)
.expect("intersection_by failed");
assert!(n <= group1.len().min(group2.len()));
let common_from_api: HashSet<_> = out1[..n].iter().map(|&i| group1[i].name).collect();
let expected: HashSet<_> = group1
.iter()
.map(|p| p.name)
.collect::<HashSet<_>>()
.intersection(&group2.iter().map(|p| p.name).collect())
.cloned()
.collect();
assert_eq!(common_from_api, expected);
}
#[test]
#[should_panic(expected = "BadAlloc")]
fn intersection_size_checks() {
let mut indices = [0usize; 10];
let mut indices2 = [0usize; 5];
let data = vec![0x41u8; 12];
sz::intersection_by(|_: usize| &data, |_: usize| &data, 1, &mut indices, &mut indices2).unwrap();
}
#[test]
fn intersection_sequences_sharing_an_empty_string() {
let set1 = ["", "p", "q"];
let set2 = ["", "z"];
let mut positions1 = [0usize; 2];
let mut positions2 = [0usize; 2];
let matched = sz::intersection(&set1, &set2, 0, &mut positions1, &mut positions2).expect("intersection failed");
assert_eq!(matched, 1);
assert_eq!(set1[positions1[0]], set2[positions2[0]]);
}
#[test]
fn sha256_empty() {
let hash = sz::Sha256::hash(b"");
let expected = [
0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae,
0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55,
];
assert_eq!(hash, expected);
}
#[test]
fn sha256_abc() {
let hash = sz::Sha256::hash(b"abc");
let expected = [
0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03,
0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad,
];
assert_eq!(hash, expected);
}
#[test]
fn sha256_incremental() {
let mut hasher = sz::Sha256::new();
hasher.update(b"ab");
hasher.update(b"c");
let hash = hasher.digest();
let expected = [
0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03,
0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad,
];
assert_eq!(hash, expected);
}
#[test]
fn sha256_multistate_matches_single() {
for lanes_count in [1usize, 7, 8, 9, 16, 17, 33] {
let messages: Vec<Vec<u8>> = (0..lanes_count)
.map(|lane_index| vec![b'a' + (lane_index % 26) as u8; lane_index * 137 % 5000])
.collect();
let mut states = vec![sz::Sha256::new(); lanes_count];
let mut offsets = vec![0usize; lanes_count];
for slice_index in 0..3 {
let ranges: Vec<(usize, usize)> = (0..lanes_count)
.map(|lane_index| {
let remaining = messages[lane_index].len() - offsets[lane_index];
let take = if slice_index == 2 { remaining } else { remaining / 3 };
let start = offsets[lane_index];
offsets[lane_index] += take;
(start, start + take)
})
.collect();
sz::sha256_multistate_update_by(&mut states, |lane_index| {
let (start, end) = ranges[lane_index];
&messages[lane_index][start..end]
})
.expect("one chunk per lane");
}
let mut digests = vec![[0u8; 32]; lanes_count];
sz::sha256_multistate_digest(&states, &mut digests).expect("one digest per lane");
for lane_index in 0..lanes_count {
assert_eq!(digests[lane_index], sz::Sha256::hash(&messages[lane_index]));
}
}
}
#[test]
fn sha256_multistate_borrows_owned_strings() {
let heads: Vec<String> = vec!["Hello, ".into(), "Goodbye, ".into()];
let tails: Vec<Vec<u8>> = vec![b"world!".to_vec(), b"world!".to_vec()];
let mut states = vec![sz::Sha256::new(); 2];
sz::sha256_multistate_update(&mut states, &heads).expect("one chunk per lane");
sz::sha256_multistate_update(&mut states, &tails).expect("one chunk per lane");
let mut digests = vec![[0u8; sz::SHA256_DIGEST_LENGTH]; 2];
sz::sha256_multistate_digest(&states, &mut digests).expect("one digest per lane");
assert_eq!(digests[0], sz::Sha256::hash(b"Hello, world!"));
assert_eq!(digests[1], sz::Sha256::hash(b"Goodbye, world!"));
}
#[test]
fn sha256_multistate_size_checks() {
let mut states = vec![sz::Sha256::new(); 3];
let too_few: Vec<&[u8]> = vec![b"a".as_slice(), b"b".as_slice()];
assert_eq!(sz::sha256_multistate_update(&mut states, &too_few), Err(sz::Status::BadAlloc));
let mut too_few_digests = vec![[0u8; sz::SHA256_DIGEST_LENGTH]; 2];
assert_eq!(
sz::sha256_multistate_digest(&states, &mut too_few_digests),
Err(sz::Status::BadAlloc)
);
}
#[test]
fn sha256_long() {
let msg = b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
let hash = sz::Sha256::hash(msg);
let expected = [
0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39, 0xa3, 0x3c,
0xe4, 0x59, 0x64, 0xff, 0x21, 0x67, 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1,
];
assert_eq!(hash, expected);
}
#[test]
fn hmac_sha256_basic() {
let key = b"";
let message = b"";
let mac = sz::hmac_sha256(key, message);
let expected = [
0xb6, 0x13, 0x67, 0x9a, 0x08, 0x14, 0xd9, 0xec, 0x77, 0x2f, 0x95, 0xd7, 0x78, 0xc3, 0x5f, 0xc5, 0xff, 0x16,
0x97, 0xc4, 0x93, 0x71, 0x56, 0x53, 0xc6, 0xc7, 0x12, 0x14, 0x42, 0x92, 0xc5, 0xad,
];
assert_eq!(mac, expected);
}
#[test]
fn hmac_sha256_multistate_matches_single() {
for key_length in [0usize, 16, 64, 65, 200] {
let key: Vec<u8> = (0..key_length).map(|index| (index % 251) as u8).collect();
for lanes_count in [1usize, 7, 8, 9, 16, 17, 33] {
let messages: Vec<Vec<u8>> = (0..lanes_count)
.map(|lane_index| vec![b'a' + (lane_index % 26) as u8; lane_index * 137 % 5000])
.collect();
let mut states = vec![sz::Sha256::new(); lanes_count];
let mut tags = vec![[0u8; sz::SHA256_DIGEST_LENGTH]; lanes_count];
sz::hmac_sha256_multistate(&key, &messages, &mut states, &mut tags).expect("one tag per message");
for lane_index in 0..lanes_count {
assert_eq!(tags[lane_index], sz::hmac_sha256(&key, &messages[lane_index]));
}
}
}
}
#[test]
fn hmac_sha256_multistate_size_checks() {
let messages: Vec<&[u8]> = vec![b"a".as_slice(), b"b".as_slice()];
let mut states = vec![sz::Sha256::new(); 2];
let mut too_few = vec![[0u8; sz::SHA256_DIGEST_LENGTH]; 1];
assert_eq!(
sz::hmac_sha256_multistate(b"k", &messages, &mut states, &mut too_few),
Err(sz::Status::BadAlloc)
);
}
#[test]
fn hmac_sha256_rfc4231_case1() {
let key = [0x0bu8; 20];
let mac = sz::hmac_sha256(&key, b"Hi There");
let expected = [
0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0x0b, 0xf1, 0x2b, 0x88, 0x1d,
0xc2, 0x00, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7,
];
assert_eq!(mac, expected);
}
#[test]
fn hmac_sha256_short_key() {
let key = b"key";
let message = b"The quick brown fox jumps over the lazy dog";
let mac = sz::hmac_sha256(key, message);
let expected = [
0xf7, 0xbc, 0x83, 0xf4, 0x30, 0x53, 0x84, 0x24, 0xb1, 0x32, 0x98, 0xe6, 0xaa, 0x6f, 0xb1, 0x43, 0xef, 0x4d,
0x59, 0xa1, 0x49, 0x46, 0x17, 0x59, 0x97, 0x47, 0x9d, 0xbc, 0x2d, 0x1a, 0x3c, 0xd8,
];
assert_eq!(mac, expected);
}
#[test]
fn hmac_sha256_long_key() {
let key = b"this is a very long key that exceeds the SHA256 block size of 64 bytes for testing purposes";
let message = b"message";
let mac = sz::hmac_sha256(key, message);
let expected = [
0xd1, 0x3f, 0xdb, 0x7b, 0xe0, 0x9a, 0x9e, 0x07, 0x04, 0xc6, 0x5b, 0xd7, 0x85, 0xa6, 0x33, 0xbb, 0xc0, 0xee,
0x2b, 0x99, 0xef, 0xd6, 0x32, 0x2c, 0xa9, 0x4c, 0xd3, 0x2c, 0x1e, 0x45, 0x09, 0xfd,
];
assert_eq!(mac, expected);
}
fn bytes_from_hex(hex: &str, output: &mut [u8]) -> usize {
let digits = hex.as_bytes();
let written = digits.len() / 2;
for index in 0..written {
let high = (digits[index * 2] as char).to_digit(16).expect("hexadecimal digit");
let low = (digits[index * 2 + 1] as char).to_digit(16).expect("hexadecimal digit");
output[index] = ((high << 4) | low) as u8;
}
written
}
struct KnownGcmVector {
secret: &'static str,
nonce: &'static str,
associated: &'static str,
plaintext: &'static str,
ciphertext: &'static str,
tag: &'static str,
}
const KNOWN_GCM_VECTORS: [KnownGcmVector; 4] = [
KnownGcmVector {
secret: "0000000000000000000000000000000000000000000000000000000000000000",
nonce: "000000000000000000000000",
associated: "",
plaintext: "",
ciphertext: "",
tag: "530f8afbc74536b9a963b4f1c4cb738b",
},
KnownGcmVector {
secret: "0000000000000000000000000000000000000000000000000000000000000000",
nonce: "000000000000000000000000",
associated: "",
plaintext: "00000000000000000000000000000000",
ciphertext: "cea7403d4d606b6e074ec5d3baf39d18",
tag: "d0d1c8a799996bf0265b98b5d48ab919",
},
KnownGcmVector {
secret: "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308",
nonce: "cafebabefacedbaddecaf888",
associated: "",
plaintext: concat!(
"d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a72",
"1c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255"
),
ciphertext: concat!(
"522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa",
"8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015ad"
),
tag: "b094dac5d93471bdec1a502270e3cc6c",
},
KnownGcmVector {
secret: "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308",
nonce: "cafebabefacedbaddecaf888",
associated: "feedfacedeadbeeffeedfacedeadbeefabaddad2",
plaintext: concat!(
"d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a72",
"1c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39"
),
ciphertext: concat!(
"522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa",
"8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662"
),
tag: "76fc6ece0f4e1768cddf8853bb2d551b",
},
];
#[test]
fn aes256_counter_round_trip() {
let secret = [0x2Bu8; sz::AES256_KEY_LENGTH];
let nonce = [0x07u8; sz::AES256_NONCE_LENGTH];
let key = sz::Aes256CtrKey::new(&secret);
let plaintext = b"counter mode is its own inverse, so one call serves both directions";
let mut ciphertext = vec![0u8; plaintext.len()];
key.xor_into(&nonce, 0, plaintext, &mut ciphertext);
assert_ne!(ciphertext.as_slice(), &plaintext[..]);
let mut recovered = vec![0u8; plaintext.len()];
key.xor_into(&nonce, 0, &ciphertext, &mut recovered);
assert_eq!(recovered.as_slice(), &plaintext[..]);
let mut in_place = plaintext.to_vec();
key.xor_in_place(&nonce, 0, &mut in_place);
assert_eq!(in_place, ciphertext);
}
#[test]
fn aes256_counter_seek() {
let secret = [0x3Cu8; sz::AES256_KEY_LENGTH];
let nonce = [0x5Du8; sz::AES256_NONCE_LENGTH];
let key = sz::Aes256CtrKey::new(&secret);
let whole: Vec<u8> = (0..512u32).map(|index| (index * 31 + 7) as u8).collect();
let mut from_zero = vec![0u8; whole.len()];
key.xor_into(&nonce, 0, &whole, &mut from_zero);
for offset in 0..200usize {
let mut sliced = vec![0u8; whole.len() - offset];
key.xor_into(&nonce, offset as u64, &whole[offset..], &mut sliced);
assert_eq!(sliced.as_slice(), &from_zero[offset..]);
}
}
#[test]
fn aes256_gcm_known_answers() {
for vector in KNOWN_GCM_VECTORS.iter() {
let mut secret = [0u8; sz::AES256_KEY_LENGTH];
let mut nonce = [0u8; sz::AES256_NONCE_LENGTH];
let mut expected_tag = [0u8; sz::AES256_TAG_LENGTH];
let mut associated = [0u8; 32];
let mut plaintext = [0u8; 64];
let mut expected = [0u8; 64];
bytes_from_hex(vector.secret, &mut secret);
bytes_from_hex(vector.nonce, &mut nonce);
let associated_length = bytes_from_hex(vector.associated, &mut associated);
let length = bytes_from_hex(vector.plaintext, &mut plaintext);
bytes_from_hex(vector.ciphertext, &mut expected);
bytes_from_hex(vector.tag, &mut expected_tag);
let associated = &associated[..associated_length];
let key = sz::Aes256GcmKey::new(&secret);
let mut produced = vec![0u8; length];
let tag = key.encrypt_into(&nonce, associated, &plaintext[..length], &mut produced);
assert_eq!(produced.as_slice(), &expected[..length]);
assert_eq!(tag, expected_tag);
let mut recovered = vec![0u8; length];
key.decrypt_into(&nonce, associated, &produced, &mut recovered, &tag)
.expect("the genuine tag must verify");
assert_eq!(recovered.as_slice(), &plaintext[..length]);
let mut in_place = plaintext[..length].to_vec();
assert_eq!(key.encrypt_in_place(&nonce, associated, &mut in_place), expected_tag);
assert_eq!(in_place, produced);
key.decrypt_in_place(&nonce, associated, &mut in_place, &tag)
.expect("the genuine tag must verify");
assert_eq!(in_place.as_slice(), &plaintext[..length]);
}
}
#[test]
fn aes256_gcm_forged_tag() {
let secret = [0x11u8; sz::AES256_KEY_LENGTH];
let nonce = [0x22u8; sz::AES256_NONCE_LENGTH];
let associated = b"routing header";
let plaintext = b"the tag is the only thing between the reader and forged plaintext";
let key = sz::Aes256GcmKey::new(&secret);
let mut ciphertext = vec![0u8; plaintext.len()];
let tag = key.encrypt_into(&nonce, associated, plaintext, &mut ciphertext);
let mut forged_tag = tag;
forged_tag[0] ^= 0x01;
let mut recovered = vec![0xA5u8; plaintext.len()];
assert_eq!(
key.decrypt_into(&nonce, associated, &ciphertext, &mut recovered, &forged_tag),
Err(sz::AuthenticationError::TagMismatch)
);
assert!(recovered.iter().all(|byte| *byte == 0));
let mut recovered = vec![0xA5u8; plaintext.len()];
assert_eq!(
key.decrypt_into(&nonce, b"forged header", &ciphertext, &mut recovered, &tag),
Err(sz::AuthenticationError::TagMismatch)
);
assert!(recovered.iter().all(|byte| *byte == 0));
let mut recovered = vec![0u8; plaintext.len()];
key.decrypt_into(&nonce, associated, &ciphertext, &mut recovered, &tag)
.expect("the genuine tag must verify");
assert_eq!(recovered.as_slice(), &plaintext[..]);
}
#[test]
fn aes256_gcm_streaming() {
let secret = [0x9Eu8; sz::AES256_KEY_LENGTH];
let nonce = [0x4Fu8; sz::AES256_NONCE_LENGTH];
let associated = b"chunked records still authenticate their header";
let plaintext: Vec<u8> = (0..300u32).map(|index| (index * 17 + 3) as u8).collect();
let key = sz::Aes256GcmKey::new(&secret);
let mut whole = vec![0u8; plaintext.len()];
let whole_tag = key.encrypt_into(&nonce, associated, &plaintext, &mut whole);
for chunk in [1usize, 7, 15, 16, 17, 64] {
let mut streamed = vec![0u8; plaintext.len()];
let mut encryptor = sz::Aes256GcmEncryptor::new(&key, &nonce);
encryptor.associate(associated);
for offset in (0..plaintext.len()).step_by(chunk) {
let taken = chunk.min(plaintext.len() - offset);
encryptor.encrypt_into(
&plaintext[offset..offset + taken],
&mut streamed[offset..offset + taken],
);
}
assert_eq!(streamed, whole);
assert_eq!(encryptor.digest(), whole_tag);
let mut recovered = vec![0u8; plaintext.len()];
let mut decryptor = sz::Aes256GcmDecryptor::new(&key, &nonce);
decryptor.associate(associated);
for offset in (0..plaintext.len()).step_by(chunk) {
let taken = chunk.min(plaintext.len() - offset);
decryptor
.decrypt_unverified_into(&whole[offset..offset + taken], &mut recovered[offset..offset + taken]);
}
decryptor.verify(&whole_tag).expect("the genuine tag must verify");
assert_eq!(recovered, plaintext);
}
let mut forged_tag = whole_tag;
forged_tag[15] ^= 0x80;
let mut recovered = vec![0u8; plaintext.len()];
let mut decryptor = sz::Aes256GcmDecryptor::new(&key, &nonce);
decryptor.associate(associated);
decryptor.decrypt_unverified_into(&whole, &mut recovered);
assert_eq!(decryptor.verify(&forged_tag), Err(sz::AuthenticationError::TagMismatch));
assert_eq!(recovered, plaintext);
}
#[test]
fn aes256_gcm_streaming_scrubs_on_drop() {
use core::mem::{size_of, MaybeUninit};
let secret = [0xA5u8; sz::AES256_KEY_LENGTH];
let nonce = [0x5Au8; sz::AES256_NONCE_LENGTH];
let key = sz::Aes256GcmKey::new(&secret);
let mut slot = MaybeUninit::<sz::Aes256GcmEncryptor>::uninit();
unsafe { slot.as_mut_ptr().write(sz::Aes256GcmEncryptor::new(&key, &nonce)) };
let mut text = *b"material that must not survive the drop";
unsafe { (*slot.as_mut_ptr()).encrypt_in_place(&mut text) };
unsafe { core::ptr::drop_in_place(slot.as_mut_ptr()) };
let payload =
unsafe { core::slice::from_raw_parts(slot.as_ptr() as *const u8, size_of::<sz::Aes256GcmEncryptor>()) };
let named_fields = &payload[..payload.len() - 6];
let surviving = named_fields.iter().filter(|byte| **byte != 0).count();
assert_eq!(
surviving, 0,
"{surviving} bytes of a dropped encryptor's payload survived"
);
}
#[test]
fn aes256_gcm_decryptor_alone() {
let secret = [0x6Du8; sz::AES256_KEY_LENGTH];
let nonce = [0x1Au8; sz::AES256_NONCE_LENGTH];
let associated = b"a header the caller happens to hold in two pieces";
let plaintext: Vec<u8> = (0..201u32).map(|index| (index * 29 + 11) as u8).collect();
let key = sz::Aes256GcmKey::new(&secret);
let mut ciphertext = vec![0u8; plaintext.len()];
let tag = key.encrypt_into(&nonce, associated, &plaintext, &mut ciphertext);
let mut opened = vec![0u8; plaintext.len()];
let mut decryptor = sz::Aes256GcmDecryptor::new(&key, &nonce);
decryptor.associate(&associated[..17]);
decryptor.associate(&associated[17..]);
for offset in (0..ciphertext.len()).step_by(23) {
let taken = 23.min(ciphertext.len() - offset);
decryptor.decrypt_unverified_into(&ciphertext[offset..offset + taken], &mut opened[offset..offset + taken]);
}
decryptor.verify(&tag).expect("the genuine tag must verify");
assert_eq!(opened, plaintext);
let mut opened_in_place = ciphertext.clone();
let mut decryptor = sz::Aes256GcmDecryptor::new(&key, &nonce);
decryptor.associate(associated);
for chunk in opened_in_place.chunks_mut(23) {
decryptor.decrypt_unverified_in_place(chunk);
}
decryptor.verify(&tag).expect("the genuine tag must verify");
assert_eq!(opened_in_place, plaintext);
let mut unassociated = ciphertext.clone();
let mut decryptor = sz::Aes256GcmDecryptor::new(&key, &nonce);
decryptor.decrypt_unverified_in_place(&mut unassociated);
assert_eq!(decryptor.verify(&tag), Err(sz::AuthenticationError::TagMismatch));
let mut wrong_nonce = nonce;
wrong_nonce[0] ^= 0x01;
let mut mismatched = ciphertext.clone();
let mut decryptor = sz::Aes256GcmDecryptor::new(&key, &wrong_nonce);
decryptor.associate(associated);
decryptor.decrypt_unverified_in_place(&mut mismatched);
assert_eq!(decryptor.verify(&tag), Err(sz::AuthenticationError::TagMismatch));
assert_ne!(mismatched, plaintext);
}
#[test]
#[should_panic(expected = "target must be at least as long as source")]
fn copy_size_checks() {
let long: Vec<u8> = vec![0; 20];
let mut less_long: Vec<u8> = vec![0; 10];
sz::copy(&mut less_long, &long);
}
#[test]
#[should_panic(expected = "target must be at least as long as source")]
fn move_size_checks() {
let long: Vec<u8> = vec![0; 20];
let mut less_long: Vec<u8> = vec![0; 10];
sz::move_(&mut less_long, &long);
}
#[test]
#[should_panic(expected = "target must be at least as long as source")]
fn lookup_size_checks() {
let long: Vec<u8> = vec![0; 20];
let mut less_long: Vec<u8> = vec![0; 10];
let lut: [u8; 256] = (0..=255u8).collect::<Vec<_>>().try_into().unwrap();
sz::lookup(&mut less_long, &long, lut);
}
#[test]
#[cfg(feature = "std")]
fn replace_all_same_length() {
let mut buffer = b"abcabc".to_vec();
let replaced = sz::try_replace_all(&mut buffer, b"ab", b"XY").expect("try_replace_all failed");
assert_eq!(replaced, 2);
assert_eq!(buffer, b"XYcXYc");
}
#[test]
#[cfg(feature = "std")]
fn replace_all_shrinks() {
let mut buffer = b"aaaa".to_vec();
let replaced = sz::try_replace_all(&mut buffer, b"aa", b"b").expect("try_replace_all failed");
assert_eq!(replaced, 2);
assert_eq!(buffer, b"bb");
}
#[test]
#[cfg(feature = "std")]
fn replace_all_grows() {
let mut buffer = b"aba".to_vec();
let replaced = sz::try_replace_all(&mut buffer, b"a", b"XYZ").expect("try_replace_all failed");
assert_eq!(replaced, 2);
assert_eq!(buffer, b"XYZbXYZ");
}
#[test]
#[cfg(feature = "std")]
fn replace_all_byteset_basic() {
let mut buffer = b"hello world".to_vec();
let vowels = sz::Byteset::from("aeiou");
let replaced = sz::try_replace_all_byteset(&mut buffer, vowels, b"_").expect("try_replace_all_byteset failed");
assert_eq!(replaced, 3);
assert_eq!(buffer, b"h_ll_ w_rld");
}
#[test]
#[cfg(feature = "std")]
fn replace_all_byteset_grows() {
let mut buffer = b"yzz".to_vec();
let vowels = sz::Byteset::from("y");
let replaced =
sz::try_replace_all_byteset(&mut buffer, vowels, b"(y)").expect("try_replace_all_byteset failed");
assert_eq!(replaced, 1);
assert_eq!(buffer, b"(y)zz");
}
#[test]
#[cfg(feature = "std")]
fn replace_all_noop_on_empty_pattern() {
let mut buffer = b"unchanged".to_vec();
let replaced = sz::try_replace_all(&mut buffer, b"", b"anything").expect("try_replace_all failed");
assert_eq!(replaced, 0);
assert_eq!(buffer, b"unchanged");
}
#[test]
fn iter_newline_utf8_splits() {
let text = b"a\nb\r\nc\n\nd";
let lines: Vec<_> = Utf8SplitNewlines::new(text).collect();
assert_eq!(lines, vec![b"a", b"b", b"c", &b""[..], b"d"]);
}
#[test]
fn iter_newline_utf8_splits_unicode() {
let text = "Hello\u{2028}World".as_bytes(); let lines: Vec<_> = Utf8SplitNewlines::new(text).collect();
assert_eq!(lines, vec!["Hello".as_bytes(), "World".as_bytes()]);
}
#[test]
fn iter_whitespace_utf8_splits() {
let text = b" a \t b\n\nc ";
let segments: Vec<_> = Utf8SplitWhitespaces::new(text).collect();
assert_eq!(
segments,
vec![
&b""[..],
&b""[..],
b"a",
&b""[..],
&b""[..],
b"b",
&b""[..],
b"c",
&b""[..],
&b""[..],
]
);
let tokens: Vec<_> = Utf8SplitWhitespaces::new(text).skip_empty().collect();
assert_eq!(tokens, vec![b"a", b"b", b"c"]);
}
#[test]
fn iter_whitespace_utf8_splits_keep_default() {
let text = b" hi ";
let kept: Vec<_> = Utf8SplitWhitespaces::new(text).collect();
assert_eq!(kept, vec![&b""[..], &b""[..], b"hi", &b""[..], &b""[..]]);
let tokens: Vec<_> = Utf8SplitWhitespaces::new(text).skip_empty().collect();
assert_eq!(tokens, vec![b"hi"]);
}
#[test]
fn iter_whitespace_utf8_splits_unicode() {
let text = "a\u{3000}b\u{2000}c".as_bytes(); let segments: Vec<_> = Utf8SplitWhitespaces::new(text).collect();
assert_eq!(segments, vec![b"a", b"b", b"c"]); let tokens: Vec<_> = Utf8SplitWhitespaces::new(text).skip_empty().collect();
assert_eq!(tokens, vec![b"a", b"b", b"c"]);
}
#[test]
fn iter_whitespace_utf8_splits_skip_empty_all_whitespace() {
let text = b" \t ";
let kept: Vec<_> = Utf8SplitWhitespaces::new(text).collect();
assert_eq!(kept.len(), 7); assert!(kept.iter().all(|segment| segment.is_empty()));
let tokens: Vec<&[u8]> = Utf8SplitWhitespaces::new(text).skip_empty().collect();
assert!(tokens.is_empty());
}
#[test]
fn iter_newline_utf8_splits_skip_empty() {
let text = b"a\nb\r\nc\n\nd";
let kept: Vec<_> = Utf8SplitNewlines::new(text).collect();
assert_eq!(kept, vec![b"a", b"b", b"c", &b""[..], b"d"]);
let nonempty: Vec<_> = Utf8SplitNewlines::new(text).skip_empty().collect();
assert_eq!(nonempty, vec![b"a", b"b", b"c", b"d"]);
}
#[test]
fn iter_newline_utf8_splits_steps_invariance() {
let text = b"\r\na\r\n\r\nb\r\nc\nd\n";
let expected: Vec<&[u8]> = vec![b"", b"a", b"", b"b", b"c", b"d", b""];
let from_1: Vec<_> = Utf8SplitNewlines::<1>::with_steps(text).collect();
let from_3: Vec<_> = Utf8SplitNewlines::<3>::with_steps(text).collect();
let from_65: Vec<_> = Utf8SplitNewlines::<65>::with_steps(text).collect();
assert_eq!(from_1, expected);
assert_eq!(from_3, expected);
assert_eq!(from_65, expected);
let nonempty: Vec<&[u8]> = vec![b"a", b"b", b"c", b"d"];
assert_eq!(
Utf8SplitNewlines::<1>::with_steps(text)
.skip_empty()
.collect::<Vec<_>>(),
nonempty
);
assert_eq!(
Utf8SplitNewlines::<3>::with_steps(text)
.skip_empty()
.collect::<Vec<_>>(),
nonempty
);
assert_eq!(
Utf8SplitNewlines::<65>::with_steps(text)
.skip_empty()
.collect::<Vec<_>>(),
nonempty
);
}
#[test]
fn iter_whitespace_utf8_splits_steps_invariance() {
let text = b" a \t b\n\nc ";
let expected: Vec<&[u8]> = vec![b"", b"", b"a", b"", b"", b"b", b"", b"c", b"", b""];
assert_eq!(
Utf8SplitWhitespaces::<1>::with_steps(text).collect::<Vec<_>>(),
expected
);
assert_eq!(
Utf8SplitWhitespaces::<3>::with_steps(text).collect::<Vec<_>>(),
expected
);
assert_eq!(
Utf8SplitWhitespaces::<65>::with_steps(text).collect::<Vec<_>>(),
expected
);
let tokens: Vec<&[u8]> = vec![b"a", b"b", b"c"];
assert_eq!(
Utf8SplitWhitespaces::<1>::with_steps(text)
.skip_empty()
.collect::<Vec<_>>(),
tokens
);
}
#[test]
fn iter_newline_utf8_splits_trailing_newline() {
let text = b"\r\na\r\n\r\nb\r\n";
let lines: Vec<&[u8]> = Utf8SplitNewlines::new(text).collect();
assert_eq!(lines.len(), 5, "Expected 5 lines");
let expected: Vec<&[u8]> = vec![b"", b"a", b"", b"b", b""];
assert_eq!(lines, expected);
}
#[test]
fn iter_newline_utf8_splits_no_trailing() {
let text = b"a\nb\nc";
let lines: Vec<&[u8]> = Utf8SplitNewlines::new(text).collect();
assert_eq!(lines.len(), 3);
assert_eq!(lines, vec![b"a", b"b", b"c"]);
}
#[test]
fn iter_newline_utf8_splits_empty_string() {
let text = b"";
let lines: Vec<&[u8]> = Utf8SplitNewlines::new(text).collect();
assert_eq!(lines.len(), 1);
assert_eq!(lines, vec![b""]);
}
#[test]
fn iter_newline_utf8_splits_single_newline() {
let text = b"\n";
let lines: Vec<&[u8]> = Utf8SplitNewlines::new(text).collect();
assert_eq!(lines.len(), 2);
assert_eq!(lines, vec![b"", b""]);
}
fn assert_steps_invariant<Kernel: SegmenterKernel>(text: &[u8]) {
let forward: Vec<&[u8]> = Utf8Segments::<Kernel, ITERATORS_DEFAULT_STEPS>::new(text).collect();
assert_eq!(Utf8Segments::<Kernel, 1>::with_steps(text).collect::<Vec<_>>(), forward);
assert_eq!(Utf8Segments::<Kernel, 3>::with_steps(text).collect::<Vec<_>>(), forward);
assert_eq!(
Utf8Segments::<Kernel, 65>::with_steps(text).collect::<Vec<_>>(),
forward
);
}
#[test]
fn iter_word_utf8_splits_steps_invariance() {
assert_steps_invariant::<Wordbreaks>(b"Hi, world! A second sentence.");
}
#[test]
fn iter_grapheme_utf8_splits_steps_invariance() {
assert_steps_invariant::<Graphemes>(b"Hi, world! A second sentence.");
}
#[test]
fn iter_sentence_utf8_splits_steps_invariance() {
assert_steps_invariant::<Sentences>(b"Hi, world! A second sentence.");
}
#[test]
fn iter_linebreak_utf8_splits_steps_invariance() {
assert_steps_invariant::<Linebreaks>(b"Hi, world! A second sentence.");
}
#[test]
fn utf8_uncased_fold_golden_vectors() {
let golden: &[(&str, &[u8])] = &[
("HeLLo", b"hello"), ("ABCDEFGHIJKLMNOPQRSTUVWXYZ", b"abcdefghijklmnopqrstuvwxyz"), ("Hello, WASM World! 12345.", b"hello, wasm world! 12345."), (
"LONG ASCII PREFIX \u{00C4} SUFFIX",
"long ascii prefix \u{00E4} suffix".as_bytes(),
),
("\u{00DF}", b"ss"), ("\u{1E9E}", b"ss"), ("\u{03A3}", "\u{03C3}".as_bytes()), ("\u{03C2}", "\u{03C3}".as_bytes()), ("\u{FB03}", b"ffi"), ("\u{041A}", "\u{043A}".as_bytes()), ("\u{00C4}", "\u{00E4}".as_bytes()), ("\u{0110}", "\u{0111}".as_bytes()), ("\u{0111}", "\u{0111}".as_bytes()), ("\u{01A0}", "\u{01A1}".as_bytes()), ("\u{01A1}", "\u{01A1}".as_bytes()), ("\u{1EA0}", "\u{1EA1}".as_bytes()), ("\u{1EA1}", "\u{1EA1}".as_bytes()), ("\u{212A}", b"k"), ("\u{10D50}", "\u{10D70}".as_bytes()), ];
for (source, expected) in golden {
let mut destination = vec![0u8; source.len() * 3];
let folded_length = sz::utf8_uncased_fold(source, &mut destination[..]);
assert_eq!(&destination[..folded_length], *expected, "folding {:?}", source);
}
let mut destination = [0u8; 16];
assert_eq!(sz::utf8_uncased_fold("\u{1E9E}", &mut destination), 2);
let folded_length = sz::utf8_uncased_fold("\u{0390}", &mut destination);
assert_eq!(folded_length, 6);
assert_eq!(&destination[..folded_length], "\u{03B9}\u{0308}\u{0301}".as_bytes());
}
#[test]
fn utf8_uncased_search_crossing_expansions() {
let cases: &[(&str, &str)] = &[
("\u{00DF}\u{00DF}", "sss"), ("\u{00DF}\u{00DF}", "\u{017F}\u{00DF}"), ("\u{1E9E}\u{00DF}", "ssss"), ("\u{1E9E}\u{00DF}", "sss"), ("\u{FB03}", "fi"), ("\u{FB03}", "ffi"), ("\u{FB00}\u{FB01}", "ffi"), ];
let paddings: &[usize] = &[0, 30, 62, 63, 64, 65];
for (haystack_core, needle) in cases {
for &padding in paddings {
let mut haystack = String::with_capacity(padding + haystack_core.len());
for _ in 0..padding {
haystack.push('z'); }
haystack.push_str(haystack_core);
let actual = sz::utf8_uncased_search(haystack.as_bytes(), needle.as_bytes());
let expected = reference_uncased_find(&haystack, needle);
assert_eq!(
actual, expected,
"mismatch for haystack_core={:?} needle={:?} padding={}",
haystack_core, needle, padding
);
}
}
}
#[test]
fn utf8_uncased_matches_empty_needle() {
let matches: Vec<_> = Utf8UncasedMatches::new(b"abc", b"").collect();
assert_eq!(matches.len(), 4);
assert!(matches.iter().all(|span| span.length == 0));
}
#[test]
fn utf8_norm_golden_vectors() {
for form in [
Utf8NormalForm::Nfd,
Utf8NormalForm::Nfc,
Utf8NormalForm::Nfkd,
Utf8NormalForm::Nfkc,
] {
let source = "Hello, world! 123";
let mut dest = vec![0u8; source.len() * 18];
let len = sz::utf8_norm(source, form, &mut dest);
assert_eq!(&dest[..len], source.as_bytes(), "ASCII unchanged under {:?}", form);
}
{
let mut dest = vec![0u8; CAFE_NFC.len() * 18];
let len = sz::utf8_norm(CAFE_NFC, Utf8NormalForm::Nfc, &mut dest);
assert_eq!(&dest[..len], CAFE_NFC.as_bytes(), "café NFC→NFC unchanged");
}
{
let mut dest = vec![0u8; CAFE_NFD.len() * 18];
let len = sz::utf8_norm(CAFE_NFD, Utf8NormalForm::Nfc, &mut dest);
assert_eq!(&dest[..len], CAFE_NFC.as_bytes(), "café NFD→NFC gives precomposed form");
}
{
let mut dest = vec![0u8; CAFE_NFC.len() * 18];
let len = sz::utf8_norm(CAFE_NFC, Utf8NormalForm::Nfd, &mut dest);
assert_eq!(&dest[..len], CAFE_NFD.as_bytes(), "café NFC→NFD gives decomposed form");
}
let ligature = "\u{FB03}"; {
let mut dest = vec![0u8; ligature.len() * 18];
let len = sz::utf8_norm(ligature, Utf8NormalForm::Nfkd, &mut dest);
assert_eq!(&dest[..len], b"ffi", "ligature NFKD → ffi");
}
{
let mut dest = vec![0u8; ligature.len() * 18];
let len = sz::utf8_norm(ligature, Utf8NormalForm::Nfkc, &mut dest);
assert_eq!(&dest[..len], b"ffi", "ligature NFKC → ffi");
}
{
let source = CAFE_NFD;
let mut first = vec![0u8; source.len() * 18];
let first_len = sz::utf8_norm(source, Utf8NormalForm::Nfc, &mut first);
let first_result = first[..first_len].to_vec();
let mut second = vec![0u8; first_len * 18];
let second_len = sz::utf8_norm(&first_result[..], Utf8NormalForm::Nfc, &mut second);
assert_eq!(&second[..second_len], &first_result[..], "NFC is idempotent");
}
}
#[test]
fn utf8_find_denormalized() {
assert_eq!(
sz::utf8_find_denormalized(CAFE_NFC, Utf8NormalForm::Nfc),
None,
"NFC string has no NFC violation"
);
let violation = sz::utf8_find_denormalized(CAFE_NFD, Utf8NormalForm::Nfc);
assert!(violation.is_some(), "NFD string must report an NFC violation");
assert!(
violation.unwrap() >= 3,
"violation offset must be ≥ 3 (at 'e' or the combining mark)"
);
assert_eq!(
sz::utf8_find_denormalized("hello", Utf8NormalForm::Nfd),
None,
"pure ASCII has no NFD violation"
);
}
}