Skip to main content

cabin_core/
hash.rs

1//! Shared hash-encoding helpers used across the workspace.
2
3use std::fmt::Write as _;
4use std::io::{Read, Write};
5
6use sha2::{Digest, Sha256};
7
8/// Lower-case hex encoding of a digest (or any byte slice).
9pub fn hex_digest(digest: &[u8]) -> String {
10    let mut hex = String::with_capacity(2 * digest.len());
11    for byte in digest {
12        let _ = write!(hex, "{byte:02x}");
13    }
14    hex
15}
16
17/// Stream `reader` through SHA-256 in 64 KiB chunks and return the
18/// lower-case hex digest. This is the shared primitive behind every
19/// Cabin file / archive integrity check; callers own opening the
20/// file and mapping any [`std::io::Error`] into their crate's own
21/// error type (and re-attaching path context).
22///
23/// # Errors
24/// Returns the [`std::io::Error`] propagated from reading `reader`.
25pub fn hash_reader<R: Read>(mut reader: R) -> std::io::Result<String> {
26    let mut hasher = Sha256::new();
27    let mut buf = vec![0u8; 64 * 1024];
28    loop {
29        let n = reader.read(&mut buf)?;
30        if n == 0 {
31            break;
32        }
33        hasher.update(&buf[..n]);
34    }
35    Ok(hex_digest(&hasher.finalize()))
36}
37
38/// Stream `reader` into `writer` in 64 KiB chunks, hashing the bytes
39/// with SHA-256 as they pass through, and return the lower-case hex
40/// digest. This is the shared primitive behind Cabin's
41/// stream-to-temp-and-verify archive staging (download, local copy,
42/// vendor): the bytes are written exactly once while the digest is
43/// computed in the same pass, so a torn copy surfaces as a checksum
44/// mismatch rather than a silently bad archive.
45///
46/// Like [`hash_reader`], callers own opening and creating the handles
47/// and mapping any [`std::io::Error`] into their crate's own error
48/// type with path context.
49///
50/// # Errors
51/// Returns the first [`std::io::Error`] encountered while reading
52/// `reader` or writing `writer`.
53pub fn hash_copy<R: Read, W: Write>(mut reader: R, mut writer: W) -> std::io::Result<String> {
54    let mut hasher = Sha256::new();
55    let mut buf = vec![0u8; 64 * 1024];
56    loop {
57        let n = reader.read(&mut buf)?;
58        if n == 0 {
59            break;
60        }
61        hasher.update(&buf[..n]);
62        writer.write_all(&buf[..n])?;
63    }
64    Ok(hex_digest(&hasher.finalize()))
65}