mirror of
https://github.com/NotAShelf/stash.git
synced 2026-04-12 22:17:41 +00:00
stash: deduplicate Fnv1aHasher; add derive for u64 wrapper
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Ic2886815721f6eefc66a8ddacd44fb286a6a6964
This commit is contained in:
parent
a2a609f07d
commit
d643376cd7
4 changed files with 108 additions and 58 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::BinaryHeap, io::Read, time::Duration};
|
use std::{collections::BinaryHeap, hash::Hasher, io::Read, time::Duration};
|
||||||
|
|
||||||
use smol::Timer;
|
use smol::Timer;
|
||||||
use wl_clipboard_rs::{
|
use wl_clipboard_rs::{
|
||||||
|
|
@ -15,36 +15,9 @@ use wl_clipboard_rs::{
|
||||||
use crate::{
|
use crate::{
|
||||||
clipboard::{self, ClipboardData, get_serving_pid},
|
clipboard::{self, ClipboardData, get_serving_pid},
|
||||||
db::{SqliteClipboardDb, nonblocking::AsyncClipboardDb},
|
db::{SqliteClipboardDb, nonblocking::AsyncClipboardDb},
|
||||||
|
hash::Fnv1aHasher,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// FNV-1a hasher for deterministic hashing across process runs.
|
|
||||||
/// Unlike `DefaultHasher` (`SipHash`), this produces stable hashes.
|
|
||||||
struct Fnv1aHasher {
|
|
||||||
state: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Fnv1aHasher {
|
|
||||||
const FNV_OFFSET: u64 = 0xCBF29CE484222325;
|
|
||||||
const FNV_PRIME: u64 = 0x100000001B3;
|
|
||||||
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
state: Self::FNV_OFFSET,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, bytes: &[u8]) {
|
|
||||||
for byte in bytes {
|
|
||||||
self.state ^= u64::from(*byte);
|
|
||||||
self.state = self.state.wrapping_mul(Self::FNV_PRIME);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish(&self) -> u64 {
|
|
||||||
self.state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper to provide [`Ord`] implementation for `f64` by negating values.
|
/// Wrapper to provide [`Ord`] implementation for `f64` by negating values.
|
||||||
/// This allows [`BinaryHeap`], which is a max-heap, to function as a min-heap.
|
/// This allows [`BinaryHeap`], which is a max-heap, to function as a min-heap.
|
||||||
/// Also see:
|
/// Also see:
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ use std::{
|
||||||
|
|
||||||
pub mod nonblocking;
|
pub mod nonblocking;
|
||||||
|
|
||||||
|
use std::hash::Hasher;
|
||||||
|
|
||||||
|
use crate::hash::Fnv1aHasher;
|
||||||
|
|
||||||
/// Cache for process scanning results to avoid expensive `/proc` reads on every
|
/// Cache for process scanning results to avoid expensive `/proc` reads on every
|
||||||
/// store operation. TTL of 5 seconds balances freshness with performance.
|
/// store operation. TTL of 5 seconds balances freshness with performance.
|
||||||
struct ProcessCache {
|
struct ProcessCache {
|
||||||
|
|
@ -66,35 +70,6 @@ impl ProcessCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FNV-1a hasher for deterministic hashing across process runs.
|
|
||||||
/// Unlike `DefaultHasher` (`SipHash` with random seed), this produces stable
|
|
||||||
/// hashes.
|
|
||||||
pub struct Fnv1aHasher {
|
|
||||||
state: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Fnv1aHasher {
|
|
||||||
const FNV_OFFSET: u64 = 0xCBF29CE484222325;
|
|
||||||
const FNV_PRIME: u64 = 0x100000001B3;
|
|
||||||
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
state: Self::FNV_OFFSET,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write(&mut self, bytes: &[u8]) {
|
|
||||||
for byte in bytes {
|
|
||||||
self.state ^= u64::from(*byte);
|
|
||||||
self.state = self.state.wrapping_mul(Self::FNV_PRIME);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(&self) -> u64 {
|
|
||||||
self.state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use base64::prelude::*;
|
use base64::prelude::*;
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use mime_sniffer::MimeTypeSniffer;
|
use mime_sniffer::MimeTypeSniffer;
|
||||||
|
|
|
||||||
101
src/hash.rs
Normal file
101
src/hash.rs
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
/// FNV-1a hasher for deterministic hashing across process runs.
|
||||||
|
///
|
||||||
|
/// Unlike `std::collections::hash_map::DefaultHasher` (which uses SipHash
|
||||||
|
/// with a random seed), this produces stable hashes suitable for persistent
|
||||||
|
/// storage and cross-process comparison.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::hash::Hasher;
|
||||||
|
///
|
||||||
|
/// use stash::hash::Fnv1aHasher;
|
||||||
|
///
|
||||||
|
/// let mut hasher = Fnv1aHasher::new();
|
||||||
|
/// hasher.write(b"hello");
|
||||||
|
/// let hash = hasher.finish();
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Fnv1aHasher {
|
||||||
|
state: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fnv1aHasher {
|
||||||
|
const FNV_OFFSET: u64 = 0xCBF29CE484222325;
|
||||||
|
const FNV_PRIME: u64 = 0x100000001B3;
|
||||||
|
|
||||||
|
/// Creates a new hasher initialized with the FNV-1a offset basis.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
state: Self::FNV_OFFSET,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Fnv1aHasher {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hasher for Fnv1aHasher {
|
||||||
|
fn write(&mut self, bytes: &[u8]) {
|
||||||
|
for byte in bytes {
|
||||||
|
self.state ^= u64::from(*byte);
|
||||||
|
self.state = self.state.wrapping_mul(Self::FNV_PRIME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(&self) -> u64 {
|
||||||
|
self.state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::hash::Hasher;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fnv1a_basic() {
|
||||||
|
let mut hasher = Fnv1aHasher::new();
|
||||||
|
hasher.write(b"hello");
|
||||||
|
// FNV-1a hash for "hello" (little-endian u64)
|
||||||
|
assert_eq!(hasher.finish(), 0xA430D84680AABD0B);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fnv1a_empty() {
|
||||||
|
let hasher = Fnv1aHasher::new();
|
||||||
|
// Empty input should return offset basis
|
||||||
|
assert_eq!(hasher.finish(), Fnv1aHasher::FNV_OFFSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fnv1a_deterministic() {
|
||||||
|
// Same input must produce same hash
|
||||||
|
let mut h1 = Fnv1aHasher::new();
|
||||||
|
let mut h2 = Fnv1aHasher::new();
|
||||||
|
h1.write(b"test data");
|
||||||
|
h2.write(b"test data");
|
||||||
|
assert_eq!(h1.finish(), h2.finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_trait() {
|
||||||
|
let h1 = Fnv1aHasher::new();
|
||||||
|
let h2 = Fnv1aHasher::default();
|
||||||
|
assert_eq!(h1.finish(), h2.finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_copy_trait() {
|
||||||
|
let mut hasher = Fnv1aHasher::new();
|
||||||
|
hasher.write(b"data");
|
||||||
|
let copied = hasher;
|
||||||
|
// Both should have same state after copy
|
||||||
|
assert_eq!(hasher.finish(), copied.finish());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
mod clipboard;
|
mod clipboard;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod db;
|
mod db;
|
||||||
|
mod hash;
|
||||||
mod mime;
|
mod mime;
|
||||||
mod multicall;
|
mod multicall;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue