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