llm_registry_core/
checksum.rs

1//! Checksum verification and hashing algorithm support
2//!
3//! This module provides types for representing and validating checksums of assets.
4//! It supports multiple hashing algorithms to ensure data integrity and security.
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8use std::str::FromStr;
9
10use crate::error::{RegistryError, Result};
11
12/// Supported hashing algorithms for checksum verification
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "UPPERCASE")]
15pub enum HashAlgorithm {
16    /// SHA-256 (most widely supported)
17    SHA256,
18    /// SHA3-256 (newer, more secure)
19    SHA3_256,
20    /// BLAKE3 (fastest, most modern)
21    BLAKE3,
22}
23
24impl HashAlgorithm {
25    /// Get the expected length of the hash in bytes
26    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    /// Get the expected length of the hash in hexadecimal characters
35    pub fn hex_length(&self) -> usize {
36        self.hash_length() * 2
37    }
38
39    /// Validate that a hash string has the correct length for this algorithm
40    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        // Validate hexadecimal format
52        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/// Checksum for verifying asset integrity
95///
96/// Stores a hash value along with the algorithm used to compute it.
97/// This allows for verification of asset contents and detection of tampering.
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
99pub struct Checksum {
100    /// The hashing algorithm used
101    pub algorithm: HashAlgorithm,
102    /// The hash value as a hexadecimal string
103    pub value: String,
104}
105
106impl Checksum {
107    /// Create a new checksum with validation
108    ///
109    /// # Arguments
110    /// * `algorithm` - The hashing algorithm used
111    /// * `value` - The hash value as a hexadecimal string
112    ///
113    /// # Errors
114    /// Returns an error if the hash value format is invalid for the algorithm
115    pub fn new(algorithm: HashAlgorithm, value: String) -> Result<Self> {
116        // Normalize to lowercase for consistency
117        let normalized_value = value.to_lowercase();
118
119        // Validate the hash format
120        algorithm.validate_hash_format(&normalized_value)?;
121
122        Ok(Self {
123            algorithm,
124            value: normalized_value,
125        })
126    }
127
128    /// Verify if this checksum matches another checksum
129    ///
130    /// Returns true if both the algorithm and value match exactly.
131    pub fn verify(&self, other: &Checksum) -> bool {
132        self.algorithm == other.algorithm && self.value == other.value
133    }
134
135    /// Verify if this checksum matches a raw hash value
136    ///
137    /// The provided hash value will be normalized to lowercase before comparison.
138    ///
139    /// # Arguments
140    /// * `hash_value` - The hash value to compare against
141    pub fn verify_hash(&self, hash_value: &str) -> bool {
142        self.value == hash_value.to_lowercase()
143    }
144
145    /// Get a reference to the hash value
146    pub fn value(&self) -> &str {
147        &self.value
148    }
149
150    /// Get the algorithm used
151    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}