mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-07-22 06:56:44 +08:00
168 lines
3.8 KiB
Rust
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);
|
|
}
|
|
}
|