1use std::fmt::Write as _;
4use std::io::{Read, Write};
5
6use sha2::{Digest, Sha256};
7
8pub 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
17pub 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
38pub 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}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use std::io::Cursor;
71
72 const EMPTY_SHA256: &str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
74
75 #[test]
76 fn hex_digest_encodes_lower_case_hex() {
77 assert_eq!(hex_digest(&[]), "");
78 assert_eq!(hex_digest(&[0x00]), "00");
79 assert_eq!(hex_digest(&[0x00, 0x0f, 0xa5, 0xff]), "000fa5ff");
80 }
81
82 #[test]
83 fn hash_reader_matches_known_sha256_vectors() {
84 assert_eq!(hash_reader(Cursor::new(b"")).unwrap(), EMPTY_SHA256);
85 assert_eq!(
86 hash_reader(Cursor::new(b"abc")).unwrap(),
87 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
88 );
89 }
90
91 #[test]
92 fn hash_reader_streams_input_larger_than_one_chunk() {
93 let data = vec![0xabu8; 3 * 64 * 1024 + 7];
96 let expected = hex_digest(&Sha256::digest(&data));
97 assert_eq!(hash_reader(Cursor::new(&data)).unwrap(), expected);
98 }
99
100 #[test]
101 fn hash_reader_propagates_read_errors() {
102 struct FailingReader;
103 impl Read for FailingReader {
104 fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
105 Err(std::io::Error::other("read failed"))
106 }
107 }
108 let err = hash_reader(FailingReader).unwrap_err();
109 assert_eq!(err.to_string(), "read failed");
110 }
111
112 #[test]
113 fn hash_copy_writes_bytes_verbatim_and_returns_matching_digest() {
114 let data = vec![0x5au8; 64 * 1024 + 3];
115 let mut copied = Vec::new();
116 let digest = hash_copy(Cursor::new(&data), &mut copied).unwrap();
117 assert_eq!(copied, data);
118 assert_eq!(digest, hash_reader(Cursor::new(&data)).unwrap());
119 }
120
121 #[test]
122 fn hash_copy_of_empty_input_writes_nothing() {
123 let mut copied = Vec::new();
124 let digest = hash_copy(Cursor::new(b""), &mut copied).unwrap();
125 assert!(copied.is_empty());
126 assert_eq!(digest, EMPTY_SHA256);
127 }
128
129 #[test]
130 fn hash_copy_propagates_write_errors() {
131 struct FailingWriter;
132 impl Write for FailingWriter {
133 fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
134 Err(std::io::Error::other("write failed"))
135 }
136 fn flush(&mut self) -> std::io::Result<()> {
137 Ok(())
138 }
139 }
140 let err = hash_copy(Cursor::new(b"payload"), FailingWriter).unwrap_err();
141 assert_eq!(err.to_string(), "write failed");
142 }
143}