Skip to main content

triplets_core/
hash.rs

1use siphasher::sip::SipHasher;
2use std::hash::{Hash, Hasher};
3use std::path::Path;
4
5/// Run `f` against a fresh [`SipHasher`] and return the resulting digest.
6///
7/// Uses the pinned `siphasher` crate (SipHash-2-4) instead of
8/// `std::collections::hash_map::DefaultHasher`, guaranteeing stable output
9/// across Rust compiler versions and platforms.
10pub fn stable_hash_with(f: impl FnOnce(&mut SipHasher)) -> u64 {
11    let mut hasher = SipHasher::new();
12    f(&mut hasher);
13    hasher.finish()
14}
15
16/// Return a deterministic u64 hash of `value` mixed with `seed`.
17///
18/// Suitable for deriving per-record RNG seeds where `seed` is a global base
19/// (e.g. a shuffle seed constant) and `value` is a unique identifier such as
20/// a ticker symbol or record id.
21pub fn stable_hash_str(seed: u64, value: &str) -> u64 {
22    stable_hash_with(|hasher| {
23        seed.hash(hasher);
24        value.hash(hasher);
25    })
26}
27
28/// Return a deterministic u64 hash of the string form of `path` mixed with `seed`.
29pub fn stable_hash_path(seed: u64, path: &Path) -> u64 {
30    stable_hash_with(|hasher| {
31        seed.hash(hasher);
32        path.to_string_lossy().hash(hasher);
33    })
34}
35
36/// Derive a per-epoch seed by mixing an epoch counter (or constant offset) into a base seed.
37///
38/// All seed derivations that incorporate an epoch value must go through this function so the
39/// derivation strategy can be changed in one place.  Both the source-shuffling path
40/// (`base_seed ^ epoch`) and the epoch-tracker initialisation path
41/// (`base_seed ^ EPOCH_SEED_OFFSET`) are expressed as `derive_epoch_seed(base_seed, epoch)`.
42pub fn derive_epoch_seed(base_seed: u64, epoch: u64) -> u64 {
43    base_seed ^ epoch
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    /// Ensures the pinned SipHash-2-4 produces a known digest for a fixed input.
51    /// If this test fails, the hasher has changed — update the expected value.
52    #[test]
53    fn stable_hash_str_is_deterministic() {
54        let result = stable_hash_str(42, "hello");
55        assert_eq!(result, 16678829552985060110);
56    }
57
58    #[test]
59    fn stable_hash_with_produces_consistent_results() {
60        let a = stable_hash_str(0, "test");
61        let b = stable_hash_str(0, "test");
62        assert_eq!(a, b);
63    }
64}