Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use nohash_hasher::IsEnabled;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
// A hash that can cheekily store small inputs directly without hashing them.
|
||||
#[derive(
|
||||
Copy, Clone, PartialEq, Eq, PartialOrd, Ord, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive,
|
||||
)]
|
||||
#[repr(transparent)]
|
||||
pub struct CheekyHash([u8; HASH_SIZE]);
|
||||
|
||||
const HASH_SIZE: usize = std::mem::size_of::<u64>() * 2;
|
||||
const HASH_PAYLOAD: usize = HASH_SIZE - 1;
|
||||
|
||||
pub type CheekyHashSet = HashSet<CheekyHash, nohash_hasher::BuildNoHashHasher<CheekyHash>>;
|
||||
pub type CheekyHashMap<V> = HashMap<CheekyHash, V, nohash_hasher::BuildNoHashHasher<CheekyHash>>;
|
||||
pub type CheekyBTreeMap<V> = BTreeMap<CheekyHash, V>;
|
||||
|
||||
impl CheekyHash {
|
||||
pub const HASH_SIZE: usize = HASH_SIZE;
|
||||
pub const NULL: CheekyHash = CheekyHash([0u8; HASH_SIZE]);
|
||||
pub const FULL: CheekyHash = CheekyHash([u8::MAX; HASH_SIZE]);
|
||||
|
||||
pub fn new(bytes: impl AsRef<[u8]>) -> Self {
|
||||
let mut hash = [0u8; HASH_SIZE];
|
||||
let bytes = bytes.as_ref();
|
||||
|
||||
if bytes.len() <= HASH_PAYLOAD {
|
||||
hash[0] = bytes.len() as u8;
|
||||
hash[1..1 + bytes.len()].copy_from_slice(bytes);
|
||||
} else {
|
||||
let h1 = xxhash_rust::xxh3::xxh3_64(bytes).to_be_bytes();
|
||||
let h2 = farmhash::fingerprint64(bytes).to_be_bytes();
|
||||
hash[0] = bytes.len().min(u8::MAX as usize) as u8;
|
||||
hash[1..1 + std::mem::size_of::<u64>()].copy_from_slice(&h1);
|
||||
hash[1 + std::mem::size_of::<u64>()..]
|
||||
.copy_from_slice(&h2[..std::mem::size_of::<u64>() - 1]);
|
||||
}
|
||||
|
||||
CheekyHash(hash)
|
||||
}
|
||||
|
||||
pub fn deserialize(bytes: &[u8]) -> Option<Self> {
|
||||
let len = *bytes.first()?;
|
||||
let mut hash = [0u8; HASH_SIZE];
|
||||
let hash_len = 1 + (len as usize).min(HASH_PAYLOAD);
|
||||
|
||||
hash[0] = len;
|
||||
hash[1..hash_len].copy_from_slice(bytes.get(1..hash_len)?);
|
||||
Some(CheekyHash(hash))
|
||||
}
|
||||
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
#[inline(always)]
|
||||
pub fn len(&self) -> usize {
|
||||
(self.0[0] as usize).min(HASH_PAYLOAD) + 1
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.0[..self.len()]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_raw_bytes(&self) -> &[u8; HASH_SIZE] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> [u8; HASH_SIZE] {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn payload(&self) -> &[u8] {
|
||||
let len = self.0[0] as usize;
|
||||
if len <= HASH_PAYLOAD {
|
||||
&self.0[1..1 + len]
|
||||
} else {
|
||||
&self.0[1..]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn payload_len(&self) -> u8 {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
fn as_u128(&self) -> u128 {
|
||||
u128::from_be_bytes(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for CheekyHash {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for CheekyHash {
|
||||
type Err = std::num::ParseIntError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
u128::from_str_radix(s, 16).map(|n| CheekyHash(n.to_be_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CheekyHash {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:032x}", self.as_u128())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for CheekyHash {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
let len = self.0[0] as usize;
|
||||
if len <= HASH_PAYLOAD {
|
||||
state.write_u64(xxhash_rust::xxh3::xxh3_64(&self.0[1..1 + len]));
|
||||
} else {
|
||||
state.write_u64(u64::from_be_bytes(
|
||||
self.0[1..1 + std::mem::size_of::<u64>()]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IsEnabled for CheekyHash {}
|
||||
|
||||
impl ArchivedCheekyHash {
|
||||
#[inline(always)]
|
||||
pub fn as_raw_bytes(&self) -> &[u8; HASH_SIZE] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
let len = self.0[0] as usize;
|
||||
&self.0[..1 + len.min(HASH_PAYLOAD)]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn to_native(&self) -> CheekyHash {
|
||||
CheekyHash(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for CheekyHash {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let len = self.payload_len();
|
||||
let payload = self.payload();
|
||||
let payload_str = if len <= HASH_PAYLOAD as u8 {
|
||||
std::str::from_utf8(payload).unwrap_or("<non-utf8>")
|
||||
} else {
|
||||
"<hashed data>"
|
||||
};
|
||||
|
||||
f.debug_struct("CheekyHash")
|
||||
.field("length", &len)
|
||||
.field("bytes", &payload_str)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cheeky_hash_all() {
|
||||
// Test 1: Empty input
|
||||
let hash_empty = CheekyHash::new([]);
|
||||
assert_eq!(
|
||||
hash_empty.as_bytes()[0],
|
||||
0,
|
||||
"Empty input should have length 0"
|
||||
);
|
||||
assert_eq!(
|
||||
hash_empty.as_bytes().len(),
|
||||
1,
|
||||
"Empty input should only have length byte"
|
||||
);
|
||||
|
||||
// Test 2: Single byte input
|
||||
let hash_single = CheekyHash::new([42]);
|
||||
assert_eq!(
|
||||
hash_single.as_bytes()[0],
|
||||
1,
|
||||
"Single byte should have length 1"
|
||||
);
|
||||
assert_eq!(
|
||||
hash_single.as_bytes()[1],
|
||||
42,
|
||||
"Single byte value should be preserved"
|
||||
);
|
||||
assert_eq!(hash_single.as_bytes().len(), 2);
|
||||
|
||||
// Test 3: Small input (less than HASH_LEN)
|
||||
let small_data = b"hello";
|
||||
let hash_small = CheekyHash::new(small_data);
|
||||
assert_eq!(hash_small.as_bytes()[0], 5, "Length should be 5");
|
||||
assert_eq!(
|
||||
&hash_small.as_bytes()[1..6],
|
||||
small_data,
|
||||
"Small data should be stored directly"
|
||||
);
|
||||
assert_eq!(hash_small.as_bytes().len(), 6);
|
||||
|
||||
// Test 4: Input exactly at HASH_PAYLOAD boundary
|
||||
let boundary_data = vec![1u8; HASH_PAYLOAD - 1];
|
||||
let hash_boundary = CheekyHash::new(&boundary_data);
|
||||
assert_eq!(
|
||||
hash_boundary.as_bytes()[0],
|
||||
(HASH_PAYLOAD - 1) as u8,
|
||||
"Length should be HASH_LEN"
|
||||
);
|
||||
assert_eq!(
|
||||
&hash_boundary.as_bytes()[1..],
|
||||
&boundary_data[..],
|
||||
"Boundary data should be stored directly"
|
||||
);
|
||||
|
||||
// Test 5: Large input (greater than HASH_LEN) - uses hashing
|
||||
let large_data = vec![7u8; HASH_SIZE];
|
||||
let hash_large = CheekyHash::new(&large_data);
|
||||
assert_eq!(
|
||||
hash_large.as_bytes()[0],
|
||||
HASH_SIZE as u8,
|
||||
"Large data should have length byte set to HASH_LEN"
|
||||
);
|
||||
assert_eq!(
|
||||
hash_large.as_bytes().len(),
|
||||
HASH_SIZE,
|
||||
"Large data hash should be full length"
|
||||
);
|
||||
// Verify it's actually hashed (not raw data)
|
||||
assert_ne!(
|
||||
&hash_large.as_bytes()[1..],
|
||||
&large_data[..HASH_PAYLOAD],
|
||||
"Large data should be hashed, not stored directly"
|
||||
);
|
||||
|
||||
// Test 6: AsRef<[u8]> trait
|
||||
let hash = CheekyHash::new(b"test");
|
||||
let bytes_ref: &[u8] = hash.as_ref();
|
||||
assert_eq!(bytes_ref, hash.as_bytes(), "AsRef should match as_bytes");
|
||||
|
||||
// Test 7: Copy, Clone, PartialEq traits
|
||||
let hash1 = CheekyHash::new(b"identical");
|
||||
let hash2 = hash1; // Copy
|
||||
assert_eq!(hash1, hash2, "Copied hashes should be equal");
|
||||
|
||||
// Test 8: Different inputs produce different hashes
|
||||
let hash_a = CheekyHash::new(b"abc");
|
||||
let hash_b = CheekyHash::new(b"def");
|
||||
assert_ne!(
|
||||
hash_a, hash_b,
|
||||
"Different inputs should produce different hashes"
|
||||
);
|
||||
|
||||
// Test 9: Same input produces same hash (deterministic)
|
||||
let hash_x1 = CheekyHash::new(b"deterministic");
|
||||
let hash_x2 = CheekyHash::new(b"deterministic");
|
||||
assert_eq!(
|
||||
hash_x1, hash_x2,
|
||||
"Same input should produce identical hashes"
|
||||
);
|
||||
|
||||
// Test 10: Large inputs with different content produce different hashes
|
||||
let large1 = vec![1u8; 100];
|
||||
let large2 = vec![2u8; 100];
|
||||
let hash_large1 = CheekyHash::new(&large1);
|
||||
let hash_large2 = CheekyHash::new(&large2);
|
||||
assert_ne!(
|
||||
hash_large1, hash_large2,
|
||||
"Different large inputs should produce different hashes"
|
||||
);
|
||||
|
||||
// Test 11: Hash trait (can be used in HashMap/HashSet)
|
||||
use std::collections::HashMap;
|
||||
let mut map = HashMap::new();
|
||||
let key = CheekyHash::new(b"key");
|
||||
map.insert(key, "value");
|
||||
assert_eq!(
|
||||
map.get(&key),
|
||||
Some(&"value"),
|
||||
"CheekyHash should work as HashMap key"
|
||||
);
|
||||
|
||||
// Test 12: Debug trait
|
||||
let hash = CheekyHash::new(b"debug");
|
||||
let debug_str = format!("{:?}", hash);
|
||||
assert!(
|
||||
debug_str.contains("CheekyHash"),
|
||||
"Debug output should contain type name"
|
||||
);
|
||||
|
||||
// Test 13: CheekyHashSet and CheekyHashMap
|
||||
let mut cheeky_set: CheekyHashSet = CheekyHashSet::default();
|
||||
cheeky_set.insert(CheekyHash::new(b"set_item"));
|
||||
assert!(cheeky_set.contains(&CheekyHash::new(b"set_item")));
|
||||
let mut cheeky_map: CheekyHashMap<&str> = CheekyHashMap::default();
|
||||
cheeky_map.insert(CheekyHash::new(b"map_key"), "map_value");
|
||||
assert_eq!(
|
||||
cheeky_map.get(&CheekyHash::new(b"map_key")),
|
||||
Some(&"map_value")
|
||||
);
|
||||
|
||||
println!("All CheekyHash tests passed!");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user