rspack_hash/
lib.rs

1use std::{
2  fmt,
3  hash::{Hash, Hasher},
4};
5
6use md4::Digest;
7use rspack_cacheable::{cacheable, with::AsPreset};
8use smol_str::SmolStr;
9use xxhash_rust::xxh64::Xxh64;
10
11#[derive(Debug, Clone, Copy)]
12pub enum HashFunction {
13  Xxhash64,
14  MD4,
15  SHA256,
16}
17
18impl From<&str> for HashFunction {
19  fn from(value: &str) -> Self {
20    match value {
21      "xxhash64" => HashFunction::Xxhash64,
22      "md4" => HashFunction::MD4,
23      "sha256" => HashFunction::SHA256,
24      _ => unimplemented!("{}", value),
25    }
26  }
27}
28
29#[derive(Debug, Clone, Copy)]
30pub enum HashDigest {
31  Hex,
32}
33
34impl From<&str> for HashDigest {
35  fn from(value: &str) -> Self {
36    match value {
37      "hex" => HashDigest::Hex,
38      _ => unimplemented!(),
39    }
40  }
41}
42
43#[derive(Debug, Clone, Hash)]
44pub enum HashSalt {
45  None,
46  Salt(String),
47}
48
49impl From<Option<String>> for HashSalt {
50  fn from(value: Option<String>) -> Self {
51    match value {
52      Some(salt) => Self::Salt(salt),
53      None => Self::None,
54    }
55  }
56}
57
58#[derive(Clone)]
59pub enum RspackHash {
60  Xxhash64(Box<Xxh64>),
61  MD4(Box<md4::Md4>),
62  SHA256(Box<sha2::Sha256>),
63}
64
65impl fmt::Debug for RspackHash {
66  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67    match self {
68      Self::Xxhash64(_) => write!(f, "RspackHash(Xxhash64)"),
69      Self::MD4(_) => write!(f, "RspackHash(MD4)"),
70      Self::SHA256(_) => write!(f, "RspackHash(SHA256"),
71    }
72  }
73}
74
75impl RspackHash {
76  pub fn new(function: &HashFunction) -> Self {
77    match function {
78      HashFunction::Xxhash64 => Self::Xxhash64(Box::new(Xxh64::new(0))),
79      HashFunction::MD4 => Self::MD4(Box::new(md4::Md4::new())),
80      HashFunction::SHA256 => Self::SHA256(Box::new(sha2::Sha256::new())),
81    }
82  }
83
84  pub fn with_salt(function: &HashFunction, salt: &HashSalt) -> Self {
85    let mut this = Self::new(function);
86    if !matches!(salt, HashSalt::None) {
87      salt.hash(&mut this);
88    }
89    this
90  }
91
92  pub fn digest(self, digest: &HashDigest) -> RspackHashDigest {
93    // The maximum value of sha256, the largest possible hash
94    let mut result = [0; 32];
95    let len;
96
97    match self {
98      RspackHash::Xxhash64(hasher) => {
99        let buf = hasher.finish().to_be_bytes();
100        len = buf.len();
101        result[..len].copy_from_slice(&buf);
102      }
103      RspackHash::MD4(hash) => {
104        let buf = hash.finalize();
105        len = buf.len();
106        result[..len].copy_from_slice(&buf);
107      }
108      RspackHash::SHA256(hash) => {
109        let buf = hash.finalize();
110        len = buf.len();
111        result[..len].copy_from_slice(&buf);
112      }
113    }
114
115    RspackHashDigest::new(&result[..len], digest)
116  }
117}
118
119impl Hasher for RspackHash {
120  fn finish(&self) -> u64 {
121    match self {
122      RspackHash::Xxhash64(hasher) => hasher.finish(),
123      RspackHash::MD4(hasher) => {
124        // finalize take ownership, so we need to clone it
125        let hash = (**hasher).clone().finalize();
126        let msb_u64: u64 = ((hash[0] as u64) << 56)
127          | ((hash[1] as u64) << 48)
128          | ((hash[2] as u64) << 40)
129          | ((hash[3] as u64) << 32)
130          | ((hash[4] as u64) << 24)
131          | ((hash[5] as u64) << 16)
132          | ((hash[6] as u64) << 8)
133          | (hash[7] as u64);
134        msb_u64
135      }
136      RspackHash::SHA256(hasher) => {
137        let hash = (**hasher).clone().finalize();
138        let msb_u64: u64 = ((hash[0] as u64) << 56)
139          | ((hash[1] as u64) << 48)
140          | ((hash[2] as u64) << 40)
141          | ((hash[3] as u64) << 32)
142          | ((hash[4] as u64) << 24)
143          | ((hash[5] as u64) << 16)
144          | ((hash[6] as u64) << 8)
145          | (hash[7] as u64);
146        msb_u64
147      }
148    }
149  }
150
151  fn write(&mut self, bytes: &[u8]) {
152    match self {
153      RspackHash::Xxhash64(hasher) => hasher.write(bytes),
154      RspackHash::MD4(hasher) => hasher.update(bytes),
155      RspackHash::SHA256(hasher) => hasher.update(bytes),
156    }
157  }
158}
159
160#[cacheable]
161#[derive(Debug, Clone, Eq)]
162pub struct RspackHashDigest {
163  #[cacheable(with=AsPreset)]
164  encoded: SmolStr,
165}
166
167impl From<&str> for RspackHashDigest {
168  fn from(value: &str) -> Self {
169    Self {
170      encoded: value.into(),
171    }
172  }
173}
174
175impl RspackHashDigest {
176  /// `inner ` must be empty or come from a hash up to 256 bits
177  pub fn new(inner: &[u8], digest: &HashDigest) -> Self {
178    let encoded = match digest {
179      HashDigest::Hex => {
180        let mut buf = [0; 64];
181        let s = hex(inner, &mut buf);
182        s.into()
183      }
184    };
185    Self { encoded }
186  }
187
188  pub fn encoded(&self) -> &str {
189    &self.encoded
190  }
191
192  pub fn rendered(&self, length: usize) -> &str {
193    let len = self.encoded.len().min(length);
194    &self.encoded[..len]
195  }
196}
197
198impl Hash for RspackHashDigest {
199  fn hash<H: Hasher>(&self, state: &mut H) {
200    self.encoded.hash(state);
201  }
202}
203
204impl PartialEq for RspackHashDigest {
205  fn eq(&self, other: &Self) -> bool {
206    self.encoded == other.encoded
207  }
208}
209
210/// Implement our own hex that is guaranteed to be inlined.
211///
212/// This will have good performance as it is simple enough to be understood by compiler.
213#[inline]
214fn hex<'a>(data: &[u8], output: &'a mut [u8]) -> &'a str {
215  const HEX_TABLE: &[u8; 16] = b"0123456789abcdef";
216
217  assert!(data.len() * 2 <= output.len());
218
219  let mut i = 0;
220  for byte in data {
221    output[i] = HEX_TABLE[(byte >> 4) as usize];
222    output[i + 1] = HEX_TABLE[(byte & 0x0f) as usize];
223    i += 2;
224  }
225
226  // # Safety
227  //
228  // hex is always ascii
229  unsafe { std::str::from_utf8_unchecked(&output[..i]) }
230}