Skip to main content

assetpack_core/
hash.rs

1use std::{fmt, io::Read, str::FromStr};
2
3use sha3::{Digest, Sha3_256};
4
5use crate::error::{Error, Result};
6
7pub const HASH_LEN: usize = 32;
8
9#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11#[cfg_attr(feature = "serde", serde(try_from = "String", into = "String"))]
12pub struct Hash32(pub [u8; HASH_LEN]);
13
14impl Hash32 {
15  pub fn new(bytes: [u8; HASH_LEN]) -> Self {
16    Self(bytes)
17  }
18
19  pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
20    if bytes.len() != HASH_LEN {
21      return Err(Error::InvalidHashLength(bytes.len()));
22    }
23    let mut arr = [0u8; HASH_LEN];
24    arr.copy_from_slice(bytes);
25    Ok(Self(arr))
26  }
27
28  pub fn sha3_256(data: impl AsRef<[u8]>) -> Self {
29    let mut hasher = Sha3_256::new();
30    hasher.update(data.as_ref());
31    let digest = hasher.finalize();
32    Hash32::new(digest.into())
33  }
34
35  pub fn sha3_256_reader(mut reader: impl Read) -> Result<Self> {
36    let mut hasher = Sha3_256::new();
37    let mut buf = [0u8; 8192];
38    loop {
39      let n = reader.read(&mut buf)?;
40      if n == 0 {
41        break;
42      }
43      hasher.update(&buf[..n]);
44    }
45    let digest = hasher.finalize();
46    Ok(Hash32::new(digest.into()))
47  }
48
49  pub fn to_hex(&self) -> String {
50    hex::encode(self.0)
51  }
52
53  pub fn from_hex(s: &str) -> Result<Self> {
54    let bytes = hex::decode(s).map_err(|_| Error::InvalidHex(s.to_string()))?;
55    Self::from_bytes(&bytes)
56  }
57
58  pub fn as_bytes(&self) -> &[u8; HASH_LEN] {
59    &self.0
60  }
61}
62
63impl fmt::Display for Hash32 {
64  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65    write!(f, "{}", self.to_hex())
66  }
67}
68
69impl fmt::Debug for Hash32 {
70  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71    write!(f, "Hash32({})", self.to_hex())
72  }
73}
74
75impl FromStr for Hash32 {
76  type Err = Error;
77
78  fn from_str(s: &str) -> Result<Self> {
79    Hash32::from_hex(s)
80  }
81}
82
83#[cfg(feature = "serde")]
84impl From<Hash32> for String {
85  fn from(value: Hash32) -> Self {
86    value.to_hex()
87  }
88}
89
90#[cfg(feature = "serde")]
91impl TryFrom<String> for Hash32 {
92  type Error = Error;
93
94  fn try_from(value: String) -> Result<Self> {
95    Hash32::from_hex(&value)
96  }
97}
98
99#[cfg(test)]
100mod tests {
101  use rand::Rng;
102
103  use super::*;
104
105  #[test]
106  fn sha3_known_value() {
107    let hash = Hash32::sha3_256("hello");
108    assert_eq!(hash.to_hex(), "3338be694f50c5f338814986cdf0686453a888b84f424d792af4b9202398f392");
109  }
110
111  #[test]
112  fn from_hex_roundtrip() {
113    let mut bytes = [0u8; HASH_LEN];
114    rand::rng().fill_bytes(&mut bytes);
115    let h = Hash32::new(bytes);
116    let encoded = h.to_hex();
117    let decoded = Hash32::from_hex(&encoded).unwrap();
118    assert_eq!(decoded, h);
119  }
120
121  #[test]
122  fn invalid_hex_rejected() {
123    let err = Hash32::from_hex("abcd").unwrap_err();
124    assert!(matches!(err, Error::InvalidHashLength(2)));
125    let err2 = Hash32::from_hex(&"zz".repeat(32)).unwrap_err();
126    assert!(matches!(err2, Error::InvalidHex(_)));
127  }
128
129  #[test]
130  fn odd_length_hex_is_rejected() {
131    let err = Hash32::from_hex("abc").unwrap_err();
132    assert!(matches!(err, Error::InvalidHex(_)));
133  }
134
135  #[test]
136  fn from_bytes_rejects_wrong_length() {
137    let err = Hash32::from_bytes(&[0u8; HASH_LEN - 1]).unwrap_err();
138    assert!(matches!(err, Error::InvalidHashLength(_)));
139  }
140
141  #[test]
142  fn reader_digest_matches() {
143    let data = vec![42u8; 1024];
144    let expected = Hash32::sha3_256(&data);
145    let via_reader = Hash32::sha3_256_reader(&data[..]).unwrap();
146    assert_eq!(expected, via_reader);
147  }
148}