llm_registry_core/
checksum.rs1use serde::{Deserialize, Serialize};
7use std::fmt;
8use std::str::FromStr;
9
10use crate::error::{RegistryError, Result};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "UPPERCASE")]
15pub enum HashAlgorithm {
16 SHA256,
18 SHA3_256,
20 BLAKE3,
22}
23
24impl HashAlgorithm {
25 pub fn hash_length(&self) -> usize {
27 match self {
28 HashAlgorithm::SHA256 => 32,
29 HashAlgorithm::SHA3_256 => 32,
30 HashAlgorithm::BLAKE3 => 32,
31 }
32 }
33
34 pub fn hex_length(&self) -> usize {
36 self.hash_length() * 2
37 }
38
39 pub fn validate_hash_format(&self, hash: &str) -> Result<()> {
41 let expected_len = self.hex_length();
42 let actual_len = hash.len();
43
44 if actual_len != expected_len {
45 return Err(RegistryError::ValidationError(format!(
46 "Invalid hash length for {:?}: expected {} characters, got {}",
47 self, expected_len, actual_len
48 )));
49 }
50
51 if !hash.chars().all(|c| c.is_ascii_hexdigit()) {
53 return Err(RegistryError::ValidationError(format!(
54 "Invalid hash format: must be hexadecimal string"
55 )));
56 }
57
58 Ok(())
59 }
60}
61
62impl fmt::Display for HashAlgorithm {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 match self {
65 HashAlgorithm::SHA256 => write!(f, "SHA256"),
66 HashAlgorithm::SHA3_256 => write!(f, "SHA3-256"),
67 HashAlgorithm::BLAKE3 => write!(f, "BLAKE3"),
68 }
69 }
70}
71
72impl Default for HashAlgorithm {
73 fn default() -> Self {
74 HashAlgorithm::SHA256
75 }
76}
77
78impl FromStr for HashAlgorithm {
79 type Err = RegistryError;
80
81 fn from_str(s: &str) -> Result<Self> {
82 match s.to_uppercase().as_str() {
83 "SHA256" => Ok(HashAlgorithm::SHA256),
84 "SHA3-256" | "SHA3_256" => Ok(HashAlgorithm::SHA3_256),
85 "BLAKE3" => Ok(HashAlgorithm::BLAKE3),
86 _ => Err(RegistryError::ValidationError(format!(
87 "Invalid hash algorithm: {}",
88 s
89 ))),
90 }
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
99pub struct Checksum {
100 pub algorithm: HashAlgorithm,
102 pub value: String,
104}
105
106impl Checksum {
107 pub fn new(algorithm: HashAlgorithm, value: String) -> Result<Self> {
116 let normalized_value = value.to_lowercase();
118
119 algorithm.validate_hash_format(&normalized_value)?;
121
122 Ok(Self {
123 algorithm,
124 value: normalized_value,
125 })
126 }
127
128 pub fn verify(&self, other: &Checksum) -> bool {
132 self.algorithm == other.algorithm && self.value == other.value
133 }
134
135 pub fn verify_hash(&self, hash_value: &str) -> bool {
142 self.value == hash_value.to_lowercase()
143 }
144
145 pub fn value(&self) -> &str {
147 &self.value
148 }
149
150 pub fn algorithm(&self) -> HashAlgorithm {
152 self.algorithm
153 }
154}
155
156impl fmt::Display for Checksum {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 write!(f, "{}:{}", self.algorithm, self.value)
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_hash_algorithm_lengths() {
168 assert_eq!(HashAlgorithm::SHA256.hash_length(), 32);
169 assert_eq!(HashAlgorithm::SHA256.hex_length(), 64);
170 assert_eq!(HashAlgorithm::SHA3_256.hash_length(), 32);
171 assert_eq!(HashAlgorithm::BLAKE3.hash_length(), 32);
172 }
173
174 #[test]
175 fn test_hash_algorithm_validation() {
176 let valid_sha256 = "a".repeat(64);
177 assert!(HashAlgorithm::SHA256.validate_hash_format(&valid_sha256).is_ok());
178
179 let invalid_length = "a".repeat(63);
180 assert!(HashAlgorithm::SHA256.validate_hash_format(&invalid_length).is_err());
181
182 let invalid_chars = "g".repeat(64);
183 assert!(HashAlgorithm::SHA256.validate_hash_format(&invalid_chars).is_err());
184 }
185
186 #[test]
187 fn test_checksum_creation() {
188 let hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
189 let checksum = Checksum::new(HashAlgorithm::SHA256, hash.to_string()).unwrap();
190 assert_eq!(checksum.algorithm, HashAlgorithm::SHA256);
191 assert_eq!(checksum.value, hash);
192 }
193
194 #[test]
195 fn test_checksum_normalization() {
196 let hash_upper = "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855";
197 let checksum = Checksum::new(HashAlgorithm::SHA256, hash_upper.to_string()).unwrap();
198 assert_eq!(checksum.value, hash_upper.to_lowercase());
199 }
200
201 #[test]
202 fn test_checksum_verification() {
203 let hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
204 let checksum1 = Checksum::new(HashAlgorithm::SHA256, hash.to_string()).unwrap();
205 let checksum2 = Checksum::new(HashAlgorithm::SHA256, hash.to_string()).unwrap();
206 assert!(checksum1.verify(&checksum2));
207 }
208
209 #[test]
210 fn test_checksum_verify_hash() {
211 let hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
212 let checksum = Checksum::new(HashAlgorithm::SHA256, hash.to_string()).unwrap();
213 assert!(checksum.verify_hash(hash));
214 assert!(checksum.verify_hash(&hash.to_uppercase()));
215 }
216
217 #[test]
218 fn test_checksum_invalid() {
219 let invalid = "not_a_valid_hash";
220 assert!(Checksum::new(HashAlgorithm::SHA256, invalid.to_string()).is_err());
221 }
222
223 #[test]
224 fn test_checksum_display() {
225 let hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
226 let checksum = Checksum::new(HashAlgorithm::SHA256, hash.to_string()).unwrap();
227 assert_eq!(
228 checksum.to_string(),
229 format!("SHA256:{}", hash)
230 );
231 }
232}