Skip to main content

oxihuman_core/
digest_hash.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5/// A simple non-cryptographic hash digest for data fingerprinting.
6#[allow(dead_code)]
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub struct DigestHash {
9    value: u64,
10}
11
12#[allow(dead_code)]
13impl DigestHash {
14    pub const SEED: u64 = 0xcbf2_9ce4_8422_2325;
15
16    pub fn new() -> Self {
17        Self { value: Self::SEED }
18    }
19
20    pub fn from_bytes(data: &[u8]) -> Self {
21        let mut h = Self::new();
22        h.update(data);
23        h
24    }
25
26    pub fn from_text(s: &str) -> Self {
27        Self::from_bytes(s.as_bytes())
28    }
29
30    pub fn update(&mut self, data: &[u8]) {
31        for &b in data {
32            self.value ^= b as u64;
33            self.value = self.value.wrapping_mul(0x0100_0000_01b3);
34        }
35    }
36
37    pub fn update_u32(&mut self, val: u32) {
38        self.update(&val.to_le_bytes());
39    }
40
41    pub fn update_f32(&mut self, val: f32) {
42        self.update(&val.to_le_bytes());
43    }
44
45    pub fn finish(&self) -> u64 {
46        self.value
47    }
48
49    pub fn finish_u32(&self) -> u32 {
50        (self.value ^ (self.value >> 32)) as u32
51    }
52
53    pub fn combine(&self, other: &DigestHash) -> DigestHash {
54        DigestHash {
55            value: self.value ^ other.value.wrapping_mul(0x9e37_79b9_7f4a_7c15),
56        }
57    }
58
59    pub fn is_zero(&self) -> bool {
60        self.value == 0
61    }
62
63    pub fn to_hex(&self) -> String {
64        format!("{:016x}", self.value)
65    }
66}
67
68impl Default for DigestHash {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_new() {
80        let h = DigestHash::new();
81        assert_eq!(h.finish(), DigestHash::SEED);
82    }
83
84    #[test]
85    fn test_from_bytes() {
86        let h = DigestHash::from_bytes(b"hello");
87        assert_ne!(h.finish(), DigestHash::SEED);
88    }
89
90    #[test]
91    fn test_deterministic() {
92        let a = DigestHash::from_text("test");
93        let b = DigestHash::from_text("test");
94        assert_eq!(a, b);
95    }
96
97    #[test]
98    fn test_different_inputs() {
99        let a = DigestHash::from_text("alpha");
100        let b = DigestHash::from_text("beta");
101        assert_ne!(a, b);
102    }
103
104    #[test]
105    fn test_update_u32() {
106        let mut h = DigestHash::new();
107        h.update_u32(42);
108        assert_ne!(h.finish(), DigestHash::SEED);
109    }
110
111    #[test]
112    fn test_update_f32() {
113        let mut h = DigestHash::new();
114        h.update_f32(1.5);
115        assert_ne!(h.finish(), DigestHash::SEED);
116    }
117
118    #[test]
119    fn test_finish_u32() {
120        let h = DigestHash::from_text("data");
121        let _v = h.finish_u32();
122        // just ensure no panic
123    }
124
125    #[test]
126    fn test_combine() {
127        let a = DigestHash::from_text("a");
128        let b = DigestHash::from_text("b");
129        let c = a.combine(&b);
130        assert_ne!(c, a);
131        assert_ne!(c, b);
132    }
133
134    #[test]
135    fn test_to_hex() {
136        let h = DigestHash::new();
137        let hex = h.to_hex();
138        assert_eq!(hex.len(), 16);
139    }
140
141    #[test]
142    fn test_default() {
143        let h = DigestHash::default();
144        assert_eq!(h.finish(), DigestHash::SEED);
145    }
146}