1use sha2::{Digest, Sha256};
2
3pub fn hash(data: &[u8]) -> String {
6 let digest = Sha256::digest(data);
7 format!("sha256:{}", hex::encode(digest))
8}
9
10#[allow(dead_code)]
13pub fn verify(data: &[u8], expected: &str) -> Result<(), IntegrityError> {
14 let actual = hash(data);
15 if actual == expected {
16 Ok(())
17 } else {
18 Err(IntegrityError::Mismatch {
19 expected: expected.to_string(),
20 actual,
21 })
22 }
23}
24
25#[allow(dead_code)]
27#[derive(Debug)]
28pub enum IntegrityError {
29 Mismatch { expected: String, actual: String },
30}
31
32impl std::fmt::Display for IntegrityError {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 match self {
35 IntegrityError::Mismatch { expected, actual } => {
36 write!(
37 f,
38 "integrity check failed: expected {expected}, got {actual}"
39 )
40 }
41 }
42 }
43}
44
45mod hex {
47 pub fn encode(bytes: impl AsRef<[u8]>) -> String {
48 use std::fmt::Write;
49 let bytes = bytes.as_ref();
50 let mut s = String::with_capacity(bytes.len() * 2);
51 for b in bytes {
52 write!(s, "{b:02x}").unwrap();
53 }
54 s
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn hash_format() {
64 let h = hash(b"hello world");
65 assert!(h.starts_with("sha256:"));
66 assert_eq!(h.len(), "sha256:".len() + 64);
68 }
69
70 #[test]
71 fn hash_deterministic() {
72 assert_eq!(hash(b"test data"), hash(b"test data"));
73 }
74
75 #[test]
76 fn hash_known_value() {
77 let h = hash(b"");
79 assert_eq!(
80 h,
81 "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
82 );
83 }
84
85 #[test]
86 fn verify_matching() {
87 let data = b"some data";
88 let h = hash(data);
89 assert!(verify(data, &h).is_ok());
90 }
91
92 #[test]
93 fn verify_mismatch() {
94 let result = verify(
95 b"actual data",
96 "sha256:0000000000000000000000000000000000000000000000000000000000000000",
97 );
98 assert!(result.is_err());
99 }
100
101 #[test]
102 fn different_data_different_hash() {
103 assert_ne!(hash(b"foo"), hash(b"bar"));
104 }
105
106 #[test]
107 fn integrity_error_display() {
108 let err = IntegrityError::Mismatch {
109 expected: "sha256:aaa".into(),
110 actual: "sha256:bbb".into(),
111 };
112 let msg = err.to_string();
113 assert!(msg.contains("integrity check failed"));
114 assert!(msg.contains("sha256:aaa"));
115 assert!(msg.contains("sha256:bbb"));
116 }
117
118 #[test]
119 fn hex_encode_empty() {
120 assert_eq!(super::hex::encode(b""), "");
121 }
122
123 #[test]
124 fn hex_encode_known_values() {
125 assert_eq!(super::hex::encode(b"\x00\xff"), "00ff");
126 assert_eq!(super::hex::encode(b"\xde\xad\xbe\xef"), "deadbeef");
127 }
128}