Skip to main content

socket_patch_core/hash/
git_sha256.rs

1use sha2::{Digest, Sha256};
2use std::io;
3use tokio::io::AsyncReadExt;
4
5/// Compute Git-compatible SHA256 hash for a byte slice.
6///
7/// Git hashes objects as: SHA256("blob <size>\0" + content)
8pub fn compute_git_sha256_from_bytes(data: &[u8]) -> String {
9    let mut hasher = Sha256::new();
10    let header = format!("blob {}\0", data.len());
11    hasher.update(header.as_bytes());
12    hasher.update(data);
13    hex::encode(hasher.finalize())
14}
15
16/// Compute Git-compatible SHA256 hash from an async reader with known size.
17///
18/// This streams the content through the hasher without loading it all into memory.
19pub async fn compute_git_sha256_from_reader<R: tokio::io::AsyncRead + Unpin>(
20    size: u64,
21    mut reader: R,
22) -> io::Result<String> {
23    let mut hasher = Sha256::new();
24    let header = format!("blob {}\0", size);
25    hasher.update(header.as_bytes());
26
27    let mut buf = [0u8; 8192];
28    loop {
29        let n = reader.read(&mut buf).await?;
30        if n == 0 {
31            break;
32        }
33        hasher.update(&buf[..n]);
34    }
35
36    Ok(hex::encode(hasher.finalize()))
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    #[test]
44    fn test_empty_content() {
45        let hash = compute_git_sha256_from_bytes(b"");
46        // SHA256("blob 0\0") - Git-compatible hash of empty content
47        assert_eq!(hash.len(), 64);
48        // Verify it's consistent
49        assert_eq!(hash, compute_git_sha256_from_bytes(b""));
50    }
51
52    #[test]
53    fn test_hello_world() {
54        let content = b"Hello, World!";
55        let hash = compute_git_sha256_from_bytes(content);
56        assert_eq!(hash.len(), 64);
57
58        // Manually compute expected: SHA256("blob 13\0Hello, World!")
59        use sha2::{Digest, Sha256};
60        let mut expected_hasher = Sha256::new();
61        expected_hasher.update(b"blob 13\0Hello, World!");
62        let expected = hex::encode(expected_hasher.finalize());
63        assert_eq!(hash, expected);
64    }
65
66    #[test]
67    fn test_known_vector() {
68        // Known test vector: SHA256("blob 0\0")
69        use sha2::{Digest, Sha256};
70        let mut hasher = Sha256::new();
71        hasher.update(b"blob 0\0");
72        let expected = hex::encode(hasher.finalize());
73        assert_eq!(compute_git_sha256_from_bytes(b""), expected);
74    }
75
76    #[tokio::test]
77    async fn test_async_reader_matches_sync() {
78        let content = b"test content for async hashing";
79        let sync_hash = compute_git_sha256_from_bytes(content);
80
81        let cursor = tokio::io::BufReader::new(&content[..]);
82        let async_hash =
83            compute_git_sha256_from_reader(content.len() as u64, cursor)
84                .await
85                .unwrap();
86
87        assert_eq!(sync_hash, async_hash);
88    }
89}