Skip to main content

tensogram_core/
hash.rs

1// (C) Copyright 2026- ECMWF and individual contributors.
2//
3// This software is licensed under the terms of the Apache Licence Version 2.0
4// which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5// In applying this licence, ECMWF does not waive the privileges and immunities
6// granted to it by virtue of its status as an intergovernmental organisation nor
7// does it submit to any jurisdiction.
8
9use crate::error::{Result, TensogramError};
10use crate::types::HashDescriptor;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum HashAlgorithm {
14    Xxh3,
15}
16
17impl HashAlgorithm {
18    pub fn as_str(&self) -> &'static str {
19        match self {
20            HashAlgorithm::Xxh3 => "xxh3",
21        }
22    }
23
24    pub fn parse(s: &str) -> Result<Self> {
25        match s {
26            "xxh3" => Ok(HashAlgorithm::Xxh3),
27            _ => Err(TensogramError::Metadata(format!("unknown hash type: {s}"))),
28        }
29    }
30}
31
32/// Compute a hash of the given data, returning the hex-encoded digest.
33pub fn compute_hash(data: &[u8], algorithm: HashAlgorithm) -> String {
34    match algorithm {
35        HashAlgorithm::Xxh3 => {
36            let hash = xxhash_rust::xxh3::xxh3_64(data);
37            format!("{hash:016x}")
38        }
39    }
40}
41
42/// Verify a hash descriptor against data.
43///
44/// If the hash algorithm is not recognized, a warning is logged and
45/// verification is skipped (returns Ok). This ensures forward compatibility
46/// when new hash algorithms are added.
47pub fn verify_hash(data: &[u8], descriptor: &HashDescriptor) -> Result<()> {
48    let algorithm = match HashAlgorithm::parse(&descriptor.hash_type) {
49        Ok(algo) => algo,
50        Err(_) => {
51            tracing::warn!(
52                hash_type = %descriptor.hash_type,
53                "unknown hash algorithm, skipping verification"
54            );
55            return Ok(());
56        }
57    };
58    let actual = compute_hash(data, algorithm);
59    if actual != descriptor.value {
60        return Err(TensogramError::HashMismatch {
61            expected: descriptor.value.clone(),
62            actual,
63        });
64    }
65    Ok(())
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_xxh3() {
74        let data = b"hello world";
75        let hash = compute_hash(data, HashAlgorithm::Xxh3);
76        assert_eq!(hash.len(), 16); // 64-bit = 16 hex chars
77        // Verify deterministic
78        assert_eq!(hash, compute_hash(data, HashAlgorithm::Xxh3));
79    }
80
81    #[test]
82    fn test_verify_hash() {
83        let data = b"test data";
84        let hash = compute_hash(data, HashAlgorithm::Xxh3);
85        let descriptor = HashDescriptor {
86            hash_type: "xxh3".to_string(),
87            value: hash,
88        };
89        assert!(verify_hash(data, &descriptor).is_ok());
90    }
91
92    #[test]
93    fn test_verify_hash_mismatch() {
94        let data = b"test data";
95        let descriptor = HashDescriptor {
96            hash_type: "xxh3".to_string(),
97            value: "0000000000000000".to_string(),
98        };
99        assert!(verify_hash(data, &descriptor).is_err());
100    }
101
102    #[test]
103    fn test_unknown_hash_type_skips_verification() {
104        let data = b"test data";
105        let descriptor = HashDescriptor {
106            hash_type: "sha256".to_string(),
107            value: "abc123".to_string(),
108        };
109        // Unknown hash algorithms skip verification with a warning (forward compatibility)
110        assert!(verify_hash(data, &descriptor).is_ok());
111    }
112}