#[cfg(all(feature = "alloc", not(any(test, doc, feature = "std"))))]
use alloc::string::String;
use crate::base64::BASE64_TABLE_U8;
use crate::hash::block::{
block_hash, block_size, BlockHashSize, BlockHashSizes, BlockSizeRelation,
ConstrainedBlockHashSize, ConstrainedBlockHashSizes,
};
use crate::hash::parser_state::{
BlockHashParseState, ParseError, ParseErrorKind, ParseErrorOrigin,
};
use crate::macros::{invariant, optionally_unsafe};
pub(crate) mod algorithms;
pub mod block;
pub mod parser_state;
#[repr(align(8))]
#[derive(Copy, Clone)]
pub struct FuzzyHashData<const S1: usize, const S2: usize, const NORM: bool>
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
pub(crate) blockhash1: [u8; S1],
pub(crate) blockhash2: [u8; S2],
pub(crate) len_blockhash1: u8,
pub(crate) len_blockhash2: u8,
pub(crate) log_blocksize: u8,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FuzzyHashOperationError {
BlockHashOverflow,
StringizationOverflow,
}
impl core::fmt::Display for FuzzyHashOperationError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
FuzzyHashOperationError::BlockHashOverflow => {
"overflow will occur while copying the block hash"
}
FuzzyHashOperationError::StringizationOverflow => {
"overflow will occur while converting to the string representation"
}
})
}
}
#[cfg(feature = "std")]
impl std::error::Error for FuzzyHashOperationError {}
#[cfg(all(not(feature = "std"), feature = "unstable"))]
impl core::error::Error for FuzzyHashOperationError {}
#[doc(alias = "hash_from_bytes_with_last_index_internal_template")]
macro_rules! hash_from_bytes_with_last_index_internal_template_impl {
(
$str: expr, $index: expr, $norm: expr,
$log_blocksize: expr,
{ $($proc_to_prepare_blockhash1:tt)* }, $proc_to_process_sequence_1: expr,
$blockhash1: expr, $len_blockhash1: expr,
{ $($proc_to_prepare_blockhash2:tt)* }, $proc_to_process_sequence_2: expr,
$blockhash2: expr, $len_blockhash2: expr
) => {
let mut buf: &[u8] = $str;
let mut offset = match algorithms::parse_block_size_from_bytes(&mut buf) {
Ok((bs, offset)) => {
$log_blocksize = block_size::log_from_valid_internal(bs);
offset
}
Err(err) => { return Err(err); }
};
$($proc_to_prepare_blockhash1)*
let (result, parsed_len) = algorithms::parse_block_hash_from_bytes::<_, S1>(
&mut $blockhash1,
&mut $len_blockhash1,
$norm,
&mut buf, $proc_to_process_sequence_1
);
offset += parsed_len;
match result {
BlockHashParseState::MetColon => { }
BlockHashParseState::MetComma => {
return Err(ParseError(ParseErrorKind::UnexpectedCharacter, ParseErrorOrigin::BlockHash1, offset - 1));
}
BlockHashParseState::Base64Error => {
return Err(ParseError(ParseErrorKind::UnexpectedCharacter, ParseErrorOrigin::BlockHash1, offset));
}
BlockHashParseState::MetEndOfString => {
return Err(ParseError(ParseErrorKind::UnexpectedEndOfString, ParseErrorOrigin::BlockHash1, offset));
}
BlockHashParseState::OverflowError => {
return Err(ParseError(ParseErrorKind::BlockHashIsTooLong, ParseErrorOrigin::BlockHash1, offset));
}
}
$($proc_to_prepare_blockhash2)*
let (result, parsed_len) = algorithms::parse_block_hash_from_bytes::<_, S2>(
&mut $blockhash2,
&mut $len_blockhash2,
$norm,
&mut buf, $proc_to_process_sequence_2
);
offset += parsed_len;
match result {
BlockHashParseState::MetComma => { *$index = offset - 1; }
BlockHashParseState::MetEndOfString => { *$index = offset; }
BlockHashParseState::MetColon => {
return Err(ParseError(ParseErrorKind::UnexpectedCharacter, ParseErrorOrigin::BlockHash2, offset - 1));
}
BlockHashParseState::Base64Error => {
return Err(ParseError(ParseErrorKind::UnexpectedCharacter, ParseErrorOrigin::BlockHash2, offset));
}
BlockHashParseState::OverflowError => {
return Err(ParseError(ParseErrorKind::BlockHashIsTooLong, ParseErrorOrigin::BlockHash2, offset));
}
}
};
}
pub(crate) use hash_from_bytes_with_last_index_internal_template_impl as hash_from_bytes_with_last_index_internal_template;
impl<const S1: usize, const S2: usize, const NORM: bool> FuzzyHashData<S1, S2, NORM>
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
pub const MAX_BLOCK_HASH_SIZE_1: usize = S1;
pub const MAX_BLOCK_HASH_SIZE_2: usize = S2;
pub const IS_NORMALIZED_FORM: bool = NORM;
pub const IS_LONG_FORM: bool = Self::MAX_BLOCK_HASH_SIZE_2 == block_hash::FULL_SIZE;
pub fn new() -> Self {
Self {
blockhash1: [0; S1],
blockhash2: [0; S2],
len_blockhash1: 0,
len_blockhash2: 0,
log_blocksize: 0,
}
}
fn init_from_internals_raw_internal(
&mut self,
log_block_size: u8,
block_hash_1: &[u8; S1],
block_hash_2: &[u8; S2],
block_hash_1_len: u8,
block_hash_2_len: u8,
) {
debug_assert!(block_size::is_log_valid(log_block_size));
debug_assert!(block_hash_1_len as usize <= S1);
debug_assert!(block_hash_2_len as usize <= S2);
debug_assert!(algorithms::verify_block_hash_input::<S1, NORM>(
block_hash_1,
block_hash_1_len,
true,
true
));
debug_assert!(algorithms::verify_block_hash_input::<S2, NORM>(
block_hash_2,
block_hash_2_len,
true,
true
));
self.blockhash1 = *block_hash_1;
self.blockhash2 = *block_hash_2;
self.len_blockhash1 = block_hash_1_len;
self.len_blockhash2 = block_hash_2_len;
self.log_blocksize = log_block_size;
}
#[cfg(feature = "unchecked")]
#[allow(unsafe_code)]
#[inline(always)]
pub unsafe fn init_from_internals_raw_unchecked(
&mut self,
log_block_size: u8,
block_hash_1: &[u8; S1],
block_hash_2: &[u8; S2],
block_hash_1_len: u8,
block_hash_2_len: u8,
) {
self.init_from_internals_raw_internal(
log_block_size,
block_hash_1,
block_hash_2,
block_hash_1_len,
block_hash_2_len,
)
}
#[inline]
pub fn init_from_internals_raw(
&mut self,
log_block_size: u8,
block_hash_1: &[u8; S1],
block_hash_2: &[u8; S2],
block_hash_1_len: u8,
block_hash_2_len: u8,
) {
assert!(block_size::is_log_valid(log_block_size));
assert!(block_hash_1_len as usize <= S1);
assert!(block_hash_2_len as usize <= S2);
assert!(algorithms::verify_block_hash_input::<S1, NORM>(
block_hash_1,
block_hash_1_len,
true,
true
));
assert!(algorithms::verify_block_hash_input::<S2, NORM>(
block_hash_2,
block_hash_2_len,
true,
true
));
self.init_from_internals_raw_internal(
log_block_size,
block_hash_1,
block_hash_2,
block_hash_1_len,
block_hash_2_len,
);
}
#[allow(dead_code)]
fn new_from_internals_raw_internal(
log_block_size: u8,
block_hash_1: &[u8; S1],
block_hash_2: &[u8; S2],
block_hash_1_len: u8,
block_hash_2_len: u8,
) -> Self {
let mut hash = Self::new();
hash.init_from_internals_raw_internal(
log_block_size,
block_hash_1,
block_hash_2,
block_hash_1_len,
block_hash_2_len,
);
hash
}
#[cfg(feature = "unchecked")]
#[allow(unsafe_code)]
#[inline(always)]
pub unsafe fn new_from_internals_raw_unchecked(
log_block_size: u8,
block_hash_1: &[u8; S1],
block_hash_2: &[u8; S2],
block_hash_1_len: u8,
block_hash_2_len: u8,
) -> Self {
Self::new_from_internals_raw_internal(
log_block_size,
block_hash_1,
block_hash_2,
block_hash_1_len,
block_hash_2_len,
)
}
#[inline]
pub fn new_from_internals_raw(
log_block_size: u8,
block_hash_1: &[u8; S1],
block_hash_2: &[u8; S2],
block_hash_1_len: u8,
block_hash_2_len: u8,
) -> Self {
let mut hash = Self::new();
hash.init_from_internals_raw(
log_block_size,
block_hash_1,
block_hash_2,
block_hash_1_len,
block_hash_2_len,
);
hash
}
fn new_from_internals_near_raw_internal(
log_block_size: u8,
block_hash_1: &[u8],
block_hash_2: &[u8],
) -> Self {
let mut hash = Self::new();
debug_assert!(block_size::is_log_valid(log_block_size));
optionally_unsafe! {
invariant!(block_hash_1.len() <= S1);
invariant!(block_hash_2.len() <= S2);
}
hash.blockhash1[..block_hash_1.len()].clone_from_slice(block_hash_1); hash.blockhash2[..block_hash_2.len()].clone_from_slice(block_hash_2); hash.len_blockhash1 = block_hash_1.len() as u8;
hash.len_blockhash2 = block_hash_2.len() as u8;
hash.log_blocksize = log_block_size;
debug_assert!(algorithms::verify_block_hash_input::<S1, NORM>(
&hash.blockhash1,
hash.len_blockhash1,
true,
false
));
debug_assert!(algorithms::verify_block_hash_input::<S2, NORM>(
&hash.blockhash2,
hash.len_blockhash2,
true,
false
));
hash
}
#[cfg(feature = "unchecked")]
#[allow(unsafe_code)]
#[inline(always)]
pub unsafe fn new_from_internals_near_raw_unchecked(
log_block_size: u8,
block_hash_1: &[u8],
block_hash_2: &[u8],
) -> Self {
Self::new_from_internals_near_raw_internal(log_block_size, block_hash_1, block_hash_2)
}
#[inline]
pub fn new_from_internals_near_raw(
log_block_size: u8,
block_hash_1: &[u8],
block_hash_2: &[u8],
) -> Self {
assert!(block_size::is_log_valid(log_block_size));
assert!(block_hash_1.len() <= S1);
assert!(block_hash_2.len() <= S2);
let hash =
Self::new_from_internals_near_raw_internal(log_block_size, block_hash_1, block_hash_2);
assert!(algorithms::verify_block_hash_input::<S1, NORM>(
&hash.blockhash1,
hash.len_blockhash1,
true,
false
));
assert!(algorithms::verify_block_hash_input::<S2, NORM>(
&hash.blockhash2,
hash.len_blockhash2,
true,
false
));
hash
}
#[allow(dead_code)]
#[inline(always)]
fn new_from_internals_internal(
block_size: u32,
block_hash_1: &[u8],
block_hash_2: &[u8],
) -> Self {
debug_assert!(block_size::is_valid(block_size));
Self::new_from_internals_near_raw_internal(
block_size::log_from_valid_internal(block_size),
block_hash_1,
block_hash_2,
)
}
#[cfg(feature = "unchecked")]
#[allow(unsafe_code)]
#[inline(always)]
pub unsafe fn new_from_internals_unchecked(
block_size: u32,
block_hash_1: &[u8],
block_hash_2: &[u8],
) -> Self {
unsafe {
Self::new_from_internals_near_raw_unchecked(
block_size::log_from_valid_internal(block_size),
block_hash_1,
block_hash_2,
)
}
}
#[inline]
pub fn new_from_internals(block_size: u32, block_hash_1: &[u8], block_hash_2: &[u8]) -> Self {
assert!(block_size::is_valid(block_size));
Self::new_from_internals_near_raw(
block_size::log_from_valid_internal(block_size),
block_hash_1,
block_hash_2,
)
}
#[inline(always)]
pub fn log_block_size(&self) -> u8 {
self.log_blocksize
}
#[inline]
pub fn block_size(&self) -> u32 {
block_size::from_log_internal(self.log_blocksize)
}
#[inline]
pub fn block_hash_1(&self) -> &[u8] {
optionally_unsafe! {
invariant!((self.len_blockhash1 as usize) <= S1);
}
&self.blockhash1[..self.len_blockhash1 as usize] }
#[inline]
pub fn block_hash_1_as_array(&self) -> &[u8; S1] {
&self.blockhash1
}
#[inline]
pub fn block_hash_1_len(&self) -> usize {
self.len_blockhash1 as usize
}
#[inline]
pub fn block_hash_2(&self) -> &[u8] {
optionally_unsafe! {
invariant!((self.len_blockhash2 as usize) <= S2);
}
&self.blockhash2[..self.len_blockhash2 as usize] }
#[inline]
pub fn block_hash_2_as_array(&self) -> &[u8; S2] {
&self.blockhash2
}
#[inline]
pub fn block_hash_2_len(&self) -> usize {
self.len_blockhash2 as usize
}
#[inline]
pub fn len_in_str(&self) -> usize {
debug_assert!(block_size::is_log_valid(self.log_blocksize));
optionally_unsafe! {
invariant!((self.log_blocksize as usize) < block_size::NUM_VALID);
block_size::BLOCK_SIZES_STR[self.log_blocksize as usize].len() + self.len_blockhash1 as usize
+ self.len_blockhash2 as usize
+ 2
}
}
pub const MAX_LEN_IN_STR: usize = block_size::MAX_BLOCK_SIZE_LEN_IN_CHARS
+ Self::MAX_BLOCK_HASH_SIZE_1
+ Self::MAX_BLOCK_HASH_SIZE_2
+ 2;
#[cfg(feature = "alloc")]
#[allow(clippy::inherent_to_string_shadow_display)]
pub fn to_string(&self) -> String {
debug_assert!((self.len_blockhash1 as usize) <= block_hash::FULL_SIZE);
debug_assert!((self.len_blockhash2 as usize) <= block_hash::FULL_SIZE);
debug_assert!(block_size::is_log_valid(self.log_blocksize));
let mut vec = alloc::vec![0u8; self.len_in_str()];
self.store_into_bytes(vec.as_mut_slice()).unwrap();
cfg_if::cfg_if! {
if #[cfg(feature = "unsafe")] {
unsafe {
String::from_utf8_unchecked(vec)
}
}
else {
String::from_utf8(vec).unwrap()
}
}
}
pub fn store_into_bytes(&self, buffer: &mut [u8]) -> Result<usize, FuzzyHashOperationError> {
let len_in_str = self.len_in_str();
if buffer.len() < len_in_str {
return Err(FuzzyHashOperationError::StringizationOverflow);
}
optionally_unsafe! {
invariant!((self.log_blocksize as usize) < block_size::NUM_VALID);
let block_size_str =
block_size::BLOCK_SIZES_STR[self.log_blocksize as usize].as_bytes(); invariant!(block_size_str.len() <= buffer.len());
buffer[..block_size_str.len()].copy_from_slice(block_size_str); let mut i: usize = block_size_str.len();
invariant!(i < buffer.len());
buffer[i] = b':'; i += 1;
algorithms::insert_block_hash_into_bytes(
&mut buffer[i..],
&self.blockhash1,
self.len_blockhash1,
);
i += self.len_blockhash1 as usize;
invariant!(i < buffer.len());
buffer[i] = b':'; i += 1;
algorithms::insert_block_hash_into_bytes(
&mut buffer[i..],
&self.blockhash2,
self.len_blockhash2,
);
if cfg!(debug_assertions) {
i += self.len_blockhash2 as usize;
debug_assert_eq!(i, len_in_str);
}
}
Ok(len_in_str)
}
#[inline(always)]
fn from_bytes_with_last_index_internal(
str: &[u8],
index: &mut usize,
) -> Result<Self, ParseError> {
let mut fuzzy = Self::new();
hash_from_bytes_with_last_index_internal_template! {
str, index, NORM,
fuzzy.log_blocksize,
{}, #[inline(always)] |_, _| {}, fuzzy.blockhash1, fuzzy.len_blockhash1,
{}, #[inline(always)] |_, _| {}, fuzzy.blockhash2, fuzzy.len_blockhash2
}
Ok(fuzzy)
}
pub fn from_bytes_with_last_index(str: &[u8], index: &mut usize) -> Result<Self, ParseError> {
Self::from_bytes_with_last_index_internal(str, index)
}
pub fn from_bytes(str: &[u8]) -> Result<Self, ParseError> {
Self::from_bytes_with_last_index_internal(str, &mut 0usize)
}
pub fn is_normalized(&self) -> bool {
algorithms::verify_block_hash_current::<S1, NORM>(
&self.blockhash1,
self.len_blockhash1,
false,
false,
) && algorithms::verify_block_hash_current::<S2, NORM>(
&self.blockhash2,
self.len_blockhash2,
false,
false,
)
}
#[inline(always)]
fn normalize_in_place_internal<const IN_NORM: bool>(&mut self) {
algorithms::normalize_block_hash_in_place::<S1, IN_NORM>(
&mut self.blockhash1,
&mut self.len_blockhash1,
);
algorithms::normalize_block_hash_in_place::<S2, IN_NORM>(
&mut self.blockhash2,
&mut self.len_blockhash2,
);
debug_assert!(self.is_valid());
}
pub fn normalize_in_place(&mut self) {
self.normalize_in_place_internal::<NORM>();
}
#[inline]
pub fn normalize(&self) -> FuzzyHashData<S1, S2, true> {
let mut dest = FuzzyHashData {
blockhash1: self.blockhash1,
blockhash2: self.blockhash2,
len_blockhash1: self.len_blockhash1,
len_blockhash2: self.len_blockhash2,
log_blocksize: self.log_blocksize,
};
dest.normalize_in_place_internal::<NORM>();
dest
}
#[inline]
pub fn clone_normalized(&self) -> Self {
let mut new = *self;
new.normalize_in_place_internal::<NORM>();
new
}
pub fn is_valid(&self) -> bool {
block_size::is_log_valid(self.log_blocksize)
&& (self.len_blockhash1 as usize) <= S1
&& (self.len_blockhash2 as usize) <= S2
&& algorithms::verify_block_hash_input::<S1, NORM>(
&self.blockhash1,
self.len_blockhash1,
true,
true,
)
&& algorithms::verify_block_hash_input::<S2, NORM>(
&self.blockhash2,
self.len_blockhash2,
true,
true,
)
}
pub fn full_eq(&self, other: &Self) -> bool {
self.blockhash1 == other.blockhash1
&& self.blockhash2 == other.blockhash2
&& self.len_blockhash1 == other.len_blockhash1
&& self.len_blockhash2 == other.len_blockhash2
&& self.log_blocksize == other.log_blocksize
}
#[inline]
pub fn compare_block_sizes(lhs: impl AsRef<Self>, rhs: impl AsRef<Self>) -> BlockSizeRelation {
block_size::compare_sizes(lhs.as_ref().log_blocksize, rhs.as_ref().log_blocksize)
}
#[inline]
pub fn is_block_sizes_near(lhs: impl AsRef<Self>, rhs: impl AsRef<Self>) -> bool {
block_size::is_near(lhs.as_ref().log_blocksize, rhs.as_ref().log_blocksize)
}
#[inline]
pub fn is_block_sizes_near_eq(lhs: impl AsRef<Self>, rhs: impl AsRef<Self>) -> bool {
block_size::is_near_eq(lhs.as_ref().log_blocksize, rhs.as_ref().log_blocksize)
}
#[inline]
pub fn is_block_sizes_near_lt(lhs: impl AsRef<Self>, rhs: impl AsRef<Self>) -> bool {
block_size::is_near_lt(lhs.as_ref().log_blocksize, rhs.as_ref().log_blocksize)
}
#[inline]
pub fn is_block_sizes_near_gt(lhs: impl AsRef<Self>, rhs: impl AsRef<Self>) -> bool {
block_size::is_near_gt(lhs.as_ref().log_blocksize, rhs.as_ref().log_blocksize)
}
#[inline]
pub fn cmp_by_block_size(&self, other: &Self) -> core::cmp::Ordering {
u8::cmp(&self.log_blocksize, &other.log_blocksize)
}
}
impl<const S1: usize, const S2: usize, const NORM: bool> AsRef<FuzzyHashData<S1, S2, NORM>>
for FuzzyHashData<S1, S2, NORM>
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
#[inline(always)]
fn as_ref(&self) -> &FuzzyHashData<S1, S2, NORM> {
self
}
}
impl<const S1: usize, const S2: usize, const NORM: bool> Default for FuzzyHashData<S1, S2, NORM>
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
fn default() -> Self {
Self::new()
}
}
impl<const S1: usize, const S2: usize, const NORM: bool> PartialEq for FuzzyHashData<S1, S2, NORM>
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
#[inline]
fn eq(&self, other: &Self) -> bool {
if !(self.len_blockhash1 == other.len_blockhash1
&& self.len_blockhash2 == other.len_blockhash2
&& self.log_blocksize == other.log_blocksize)
{
return false;
}
optionally_unsafe! {
invariant!((self.len_blockhash1 as usize) <= self.blockhash1.len());
invariant!((self.len_blockhash2 as usize) <= self.blockhash2.len());
invariant!((other.len_blockhash1 as usize) <= other.blockhash1.len());
invariant!((other.len_blockhash2 as usize) <= other.blockhash2.len());
}
let bh1_a = &self.blockhash1[0..self.len_blockhash1 as usize]; let bh2_a = &self.blockhash2[0..self.len_blockhash2 as usize]; let bh1_b = &other.blockhash1[0..other.len_blockhash1 as usize]; let bh2_b = &other.blockhash2[0..other.len_blockhash2 as usize]; bh1_a == bh1_b && bh2_a == bh2_b
}
}
impl<const S1: usize, const S2: usize, const NORM: bool> Eq for FuzzyHashData<S1, S2, NORM>
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
}
impl<const S1: usize, const S2: usize, const NORM: bool> core::hash::Hash
for FuzzyHashData<S1, S2, NORM>
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
state.write_u8(self.log_blocksize);
state.write_u8(self.len_blockhash1);
state.write_u8(self.len_blockhash2);
optionally_unsafe! {
invariant!((self.len_blockhash1 as usize) <= self.blockhash1.len());
invariant!((self.len_blockhash2 as usize) <= self.blockhash2.len());
}
state.write(&self.blockhash1[0..self.len_blockhash1 as usize]); state.write(&self.blockhash2[0..self.len_blockhash2 as usize]); }
}
impl<const S1: usize, const S2: usize, const NORM: bool> Ord for FuzzyHashData<S1, S2, NORM>
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
#[inline]
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
(
self.log_blocksize,
&self.blockhash1,
self.len_blockhash1,
&self.blockhash2,
self.len_blockhash2,
)
.cmp(&(
other.log_blocksize,
&other.blockhash1,
other.len_blockhash1,
&other.blockhash2,
other.len_blockhash2,
))
}
}
impl<const S1: usize, const S2: usize, const NORM: bool> PartialOrd for FuzzyHashData<S1, S2, NORM>
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[cfg(feature = "alloc")]
impl<const S1: usize, const S2: usize, const NORM: bool>
core::convert::From<FuzzyHashData<S1, S2, NORM>> for String
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
fn from(value: FuzzyHashData<S1, S2, NORM>) -> Self {
value.to_string()
}
}
impl<const S1: usize, const S2: usize, const NORM: bool> core::fmt::Display
for FuzzyHashData<S1, S2, NORM>
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut buffer = [0u8; crate::MAX_LEN_IN_STR];
let len = self.store_into_bytes(&mut buffer).unwrap();
cfg_if::cfg_if! {
if #[cfg(feature = "unsafe")] {
unsafe {
f.write_str(core::str::from_utf8_unchecked(&buffer[..len]))
}
}
else {
f.write_str(core::str::from_utf8(&buffer[..len]).unwrap())
}
}
}
}
impl<const S1: usize, const S2: usize, const NORM: bool> core::fmt::Debug
for FuzzyHashData<S1, S2, NORM>
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.is_valid() {
let buffer1 = self.blockhash1.map(|x| BASE64_TABLE_U8[x as usize]); let buffer2 = self.blockhash2.map(|x| BASE64_TABLE_U8[x as usize]); f.debug_struct("FuzzyHashData")
.field("LONG", &Self::IS_LONG_FORM)
.field("NORM", &Self::IS_NORMALIZED_FORM)
.field(
"block_size",
&block_size::from_log_internal(self.log_blocksize),
)
.field(
"blockhash1",
&core::str::from_utf8(&buffer1[..self.len_blockhash1 as usize]).unwrap(),
)
.field(
"blockhash2",
&core::str::from_utf8(&buffer2[..self.len_blockhash2 as usize]).unwrap(),
)
.finish()
} else {
f.debug_struct("FuzzyHashData")
.field("ILL_FORMED", &true)
.field("LONG", &Self::IS_LONG_FORM)
.field("NORM", &Self::IS_NORMALIZED_FORM)
.field("log_blocksize", &self.log_blocksize)
.field("len_blockhash1", &self.len_blockhash1)
.field("len_blockhash2", &self.len_blockhash2)
.field("blockhash1", &self.blockhash1)
.field("blockhash2", &self.blockhash2)
.finish()
}
}
}
impl<const S1: usize, const S2: usize, const NORM: bool> core::str::FromStr
for FuzzyHashData<S1, S2, NORM>
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
type Err = ParseError;
#[inline(always)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_bytes(s.as_bytes())
}
}
#[doc(alias = "fuzzy_norm_type")]
macro_rules! norm_type {($s1: expr, $s2: expr) => { FuzzyHashData<$s1, $s2, true> }}
#[doc(alias = "fuzzy_raw_type")]
macro_rules! raw_type {($s1: expr, $s2: expr) => { FuzzyHashData<$s1, $s2, false> }}
pub(crate) use norm_type as fuzzy_norm_type;
pub(crate) use raw_type as fuzzy_raw_type;
macro_rules! short_type {($norm: expr) => {FuzzyHashData<{block_hash::FULL_SIZE}, {block_hash::HALF_SIZE}, $norm> }}
macro_rules! long_type {($norm: expr) => {FuzzyHashData<{block_hash::FULL_SIZE}, {block_hash::FULL_SIZE}, $norm> }}
impl<const S1: usize, const S2: usize> norm_type!(S1, S2)
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
#[inline]
pub fn block_hash_1_windows(&self) -> core::slice::Windows<'_, u8> {
self.block_hash_1()
.windows(block_hash::MIN_LCS_FOR_COMPARISON)
}
#[inline]
pub fn block_hash_1_numeric_windows(&self) -> block::block_hash::NumericWindows {
block::block_hash::NumericWindows::new(self.block_hash_1())
}
#[inline]
pub fn block_hash_2_windows(&self) -> core::slice::Windows<'_, u8> {
self.block_hash_2()
.windows(block_hash::MIN_LCS_FOR_COMPARISON)
}
#[inline]
pub fn block_hash_2_numeric_windows(&self) -> block::block_hash::NumericWindows {
block::block_hash::NumericWindows::new(self.block_hash_2())
}
#[inline]
pub fn from_raw_form(source: &raw_type!(S1, S2)) -> Self {
source.normalize()
}
#[inline]
pub fn to_raw_form(&self) -> raw_type!(S1, S2) {
FuzzyHashData {
blockhash1: self.blockhash1,
blockhash2: self.blockhash2,
len_blockhash1: self.len_blockhash1,
len_blockhash2: self.len_blockhash2,
log_blocksize: self.log_blocksize,
}
}
#[inline]
pub fn into_mut_raw_form(&self, dest: &mut raw_type!(S1, S2)) {
dest.blockhash1 = self.blockhash1;
dest.blockhash2 = self.blockhash2;
dest.len_blockhash1 = self.len_blockhash1;
dest.len_blockhash2 = self.len_blockhash2;
dest.log_blocksize = self.log_blocksize;
}
}
impl<const S1: usize, const S2: usize> raw_type!(S1, S2)
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
#[inline]
pub fn from_normalized(source: &norm_type!(S1, S2)) -> Self {
source.to_raw_form()
}
}
impl<const NORM: bool> short_type!(NORM) {
#[inline]
pub fn to_long_form(&self) -> long_type!(NORM) {
let mut dest = FuzzyHashData {
blockhash1: self.blockhash1,
blockhash2: [0; block_hash::FULL_SIZE],
len_blockhash1: self.len_blockhash1,
len_blockhash2: self.len_blockhash2,
log_blocksize: self.log_blocksize,
};
dest.blockhash2[0..block_hash::HALF_SIZE].copy_from_slice(&self.blockhash2);
dest
}
#[inline]
pub fn into_mut_long_form(&self, dest: &mut long_type!(NORM)) {
dest.blockhash1 = self.blockhash1;
dest.blockhash2[0..block_hash::HALF_SIZE].copy_from_slice(&self.blockhash2);
dest.blockhash2[block_hash::HALF_SIZE..block_hash::FULL_SIZE].fill(0);
dest.len_blockhash1 = self.len_blockhash1;
dest.len_blockhash2 = self.len_blockhash2;
dest.log_blocksize = self.log_blocksize;
}
}
impl<const NORM: bool> long_type!(NORM) {
#[inline]
pub fn from_short_form(source: &short_type!(NORM)) -> Self {
source.to_long_form()
}
#[inline]
pub fn try_into_mut_short(
&self,
dest: &mut short_type!(NORM),
) -> Result<(), FuzzyHashOperationError> {
if self.len_blockhash2 as usize > block_hash::HALF_SIZE {
return Err(FuzzyHashOperationError::BlockHashOverflow);
}
dest.blockhash1 = self.blockhash1;
dest.blockhash2
.copy_from_slice(&self.blockhash2[0..block_hash::HALF_SIZE]);
dest.len_blockhash1 = self.len_blockhash1;
dest.len_blockhash2 = self.len_blockhash2;
dest.log_blocksize = self.log_blocksize;
Ok(())
}
}
impl<const S1: usize, const S2: usize> core::convert::From<norm_type!(S1, S2)> for raw_type!(S1, S2)
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
#[inline]
fn from(value: norm_type!(S1, S2)) -> Self {
value.to_raw_form()
}
}
impl<const S1: usize, const S2: usize> core::convert::From<raw_type!(S1, S2)> for norm_type!(S1, S2)
where
BlockHashSize<S1>: ConstrainedBlockHashSize,
BlockHashSize<S2>: ConstrainedBlockHashSize,
BlockHashSizes<S1, S2>: ConstrainedBlockHashSizes,
{
#[inline]
fn from(value: raw_type!(S1, S2)) -> Self {
value.normalize()
}
}
impl<const NORM: bool> core::convert::From<short_type!(NORM)> for long_type!(NORM) {
#[inline]
fn from(value: short_type!(NORM)) -> Self {
value.to_long_form()
}
}
impl core::convert::From<short_type!(true)> for long_type!(false) {
#[inline]
fn from(value: short_type!(true)) -> Self {
let mut dest: Self = Self::new();
dest.blockhash1 = value.blockhash1;
dest.blockhash2[0..block_hash::HALF_SIZE].copy_from_slice(&value.blockhash2);
dest.len_blockhash1 = value.len_blockhash1;
dest.len_blockhash2 = value.len_blockhash2;
dest.log_blocksize = value.log_blocksize;
dest
}
}
impl<const NORM: bool> core::convert::TryFrom<long_type!(NORM)> for short_type!(NORM) {
type Error = FuzzyHashOperationError;
fn try_from(value: long_type!(NORM)) -> Result<Self, Self::Error> {
let mut dest: Self = Self::new();
value.try_into_mut_short(&mut dest)?;
Ok(dest)
}
}
pub type FuzzyHash = FuzzyHashData<{ block_hash::FULL_SIZE }, { block_hash::HALF_SIZE }, true>;
pub type RawFuzzyHash = FuzzyHashData<{ block_hash::FULL_SIZE }, { block_hash::HALF_SIZE }, false>;
pub type LongFuzzyHash = FuzzyHashData<{ block_hash::FULL_SIZE }, { block_hash::FULL_SIZE }, true>;
pub type LongRawFuzzyHash =
FuzzyHashData<{ block_hash::FULL_SIZE }, { block_hash::FULL_SIZE }, false>;
#[doc(hidden)]
mod const_asserts {
use super::*;
use static_assertions::{const_assert, const_assert_eq};
const_assert_eq!(FuzzyHash::MAX_BLOCK_HASH_SIZE_1, block_hash::FULL_SIZE);
const_assert_eq!(FuzzyHash::MAX_BLOCK_HASH_SIZE_2, block_hash::HALF_SIZE);
const_assert_eq!(FuzzyHash::IS_NORMALIZED_FORM, true);
const_assert_eq!(FuzzyHash::IS_LONG_FORM, false);
const_assert_eq!(RawFuzzyHash::MAX_BLOCK_HASH_SIZE_1, block_hash::FULL_SIZE);
const_assert_eq!(RawFuzzyHash::MAX_BLOCK_HASH_SIZE_2, block_hash::HALF_SIZE);
const_assert_eq!(RawFuzzyHash::IS_NORMALIZED_FORM, false);
const_assert_eq!(RawFuzzyHash::IS_LONG_FORM, false);
const_assert_eq!(LongFuzzyHash::MAX_BLOCK_HASH_SIZE_1, block_hash::FULL_SIZE);
const_assert_eq!(LongFuzzyHash::MAX_BLOCK_HASH_SIZE_2, block_hash::FULL_SIZE);
const_assert_eq!(LongFuzzyHash::IS_NORMALIZED_FORM, true);
const_assert_eq!(LongFuzzyHash::IS_LONG_FORM, true);
const_assert_eq!(
LongRawFuzzyHash::MAX_BLOCK_HASH_SIZE_1,
block_hash::FULL_SIZE
);
const_assert_eq!(
LongRawFuzzyHash::MAX_BLOCK_HASH_SIZE_2,
block_hash::FULL_SIZE
);
const_assert_eq!(LongRawFuzzyHash::IS_NORMALIZED_FORM, false);
const_assert_eq!(LongRawFuzzyHash::IS_LONG_FORM, true);
const_assert_eq!(
FuzzyHash::MAX_BLOCK_HASH_SIZE_1,
RawFuzzyHash::MAX_BLOCK_HASH_SIZE_1
);
const_assert_eq!(
FuzzyHash::MAX_BLOCK_HASH_SIZE_2,
RawFuzzyHash::MAX_BLOCK_HASH_SIZE_2
);
const_assert_eq!(FuzzyHash::MAX_LEN_IN_STR, RawFuzzyHash::MAX_LEN_IN_STR);
const_assert_eq!(
core::mem::size_of::<FuzzyHash>(),
core::mem::size_of::<RawFuzzyHash>()
);
const_assert_eq!(
LongFuzzyHash::MAX_BLOCK_HASH_SIZE_1,
LongRawFuzzyHash::MAX_BLOCK_HASH_SIZE_1
);
const_assert_eq!(
LongFuzzyHash::MAX_BLOCK_HASH_SIZE_2,
LongRawFuzzyHash::MAX_BLOCK_HASH_SIZE_2
);
const_assert_eq!(
LongFuzzyHash::MAX_LEN_IN_STR,
LongRawFuzzyHash::MAX_LEN_IN_STR
);
const_assert_eq!(
core::mem::size_of::<LongFuzzyHash>(),
core::mem::size_of::<LongRawFuzzyHash>()
);
const_assert_eq!(
FuzzyHash::MAX_BLOCK_HASH_SIZE_1,
LongFuzzyHash::MAX_BLOCK_HASH_SIZE_1
);
const_assert_eq!(
RawFuzzyHash::MAX_BLOCK_HASH_SIZE_1,
LongRawFuzzyHash::MAX_BLOCK_HASH_SIZE_1
);
const_assert!(FuzzyHash::MAX_BLOCK_HASH_SIZE_2 < LongFuzzyHash::MAX_BLOCK_HASH_SIZE_2);
const_assert!(RawFuzzyHash::MAX_BLOCK_HASH_SIZE_2 < LongRawFuzzyHash::MAX_BLOCK_HASH_SIZE_2);
const_assert!(FuzzyHash::MAX_LEN_IN_STR < LongFuzzyHash::MAX_LEN_IN_STR);
const_assert!(RawFuzzyHash::MAX_LEN_IN_STR < LongRawFuzzyHash::MAX_LEN_IN_STR);
}
pub(crate) mod test_utils;
pub(crate) mod tests;