Files
BlueArchiveToolkit/crates/bat-cas-engine/src/hash.rs
T

168 lines
3.8 KiB
Rust

//! Hash 计算模块
//!
//! 使用 BLAKE3 算法计算内容 Hash
use std::fmt;
use std::str::FromStr;
/// Hash 类型
///
/// 使用 BLAKE3 算法计算的 256 位 Hash
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Hash([u8; 32]);
impl Hash {
/// 从字节数组创建 Hash
pub fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
/// 获取 Hash 的字节表示
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
/// 转换为十六进制字符串
pub fn to_hex(&self) -> String {
hex::encode(self.0)
}
/// 从十六进制字符串解析
pub fn from_hex(s: &str) -> Result<Self, crate::error::CasError> {
let decoded =
hex::decode(s).map_err(|_| crate::error::CasError::InvalidHash(s.to_string()));
let bytes = decoded?;
if bytes.len() != 32 {
return Err(crate::error::CasError::InvalidHash(s.to_string()));
}
let mut hash_bytes = [0u8; 32];
hash_bytes.copy_from_slice(&bytes);
Ok(Self(hash_bytes))
}
}
impl fmt::Display for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_hex())
}
}
impl FromStr for Hash {
type Err = crate::error::CasError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_hex(s)
}
}
impl serde::Serialize for Hash {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_hex())
}
}
impl<'de> serde::Deserialize<'de> for Hash {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Self::from_hex(&s).map_err(serde::de::Error::custom)
}
}
/// 计算数据的 BLAKE3 Hash
///
/// # 参数
/// - `data`: 要计算 Hash 的数据
///
/// # 返回
/// - 数据的 BLAKE3 Hash
///
/// # 示例
/// ```
/// use bat_cas_engine::hash::compute_hash;
///
/// let data = b"Hello, World!";
/// let hash = compute_hash(data);
/// println!("Hash: {}", hash);
/// ```
pub fn compute_hash(data: &[u8]) -> Hash {
let hash_bytes = blake3::hash(data);
Hash::from_bytes(*hash_bytes.as_bytes())
}
/// 计算文件的 BLAKE3 Hash
///
/// # 参数
/// - `path`: 文件路径
///
/// # 返回
/// - 成功:返回文件的 Hash
/// - 失败:返回错误
pub async fn compute_file_hash(path: &std::path::Path) -> crate::error::Result<Hash> {
let data = tokio::fs::read(path).await?;
Ok(compute_hash(&data))
}
#[cfg(test)]
mod tests {
use super::*;
fn test_hash_and_hex() -> (Hash, String) {
let hash = compute_hash(b"Test data");
let hex = hash.to_hex();
(hash, hex)
}
#[test]
fn test_compute_hash() {
let data = b"Hello, World!";
let hash = compute_hash(data);
// BLAKE3 Hash 应该是确定性的
let hash2 = compute_hash(data);
assert_eq!(hash, hash2);
}
#[test]
fn test_hash_to_string() {
let (_hash, hex) = test_hash_and_hex();
// 应该是 64 个字符的十六进制字符串
assert_eq!(hex.len(), 64);
}
#[test]
fn test_hash_from_string() {
let (hash, hex) = test_hash_and_hex();
let parsed = Hash::from_hex(&hex).unwrap();
assert_eq!(hash, parsed);
}
#[test]
fn test_hash_different_data() {
let hash1 = compute_hash(b"Data 1");
let hash2 = compute_hash(b"Data 2");
assert_ne!(hash1, hash2);
}
#[test]
fn test_hash_serialization() {
let data = b"Serialization test";
let hash = compute_hash(data);
let json = serde_json::to_string(&hash).unwrap();
let deserialized: Hash = serde_json::from_str(&json).unwrap();
assert_eq!(hash, deserialized);
}
}