shell_tunnel/fs/
sha256.rs1use std::io::Read;
8use std::path::Path;
9
10use sha2::{Digest, Sha256};
11
12const READ_CHUNK: usize = 64 * 1024;
15
16pub fn hash_file(path: &Path) -> std::io::Result<String> {
18 let mut file = std::fs::File::open(path)?;
19 let mut digest = Sha256::new();
20 let mut buffer = vec![0_u8; READ_CHUNK];
21 loop {
22 let read = file.read(&mut buffer)?;
23 if read == 0 {
24 break;
25 }
26 digest.update(&buffer[..read]);
27 }
28 Ok(hex(&digest.finalize()))
29}
30
31#[derive(Debug, Default)]
33pub struct Hasher {
34 inner: Sha256,
35}
36
37impl Hasher {
38 pub fn new() -> Self {
39 Self {
40 inner: Sha256::new(),
41 }
42 }
43
44 pub fn update(&mut self, bytes: &[u8]) {
45 self.inner.update(bytes);
46 }
47
48 pub fn finish(self) -> String {
50 hex(&self.inner.finalize())
51 }
52}
53
54fn hex(bytes: &[u8]) -> String {
55 let mut out = String::with_capacity(bytes.len() * 2);
56 for byte in bytes {
57 out.push_str(&format!("{byte:02x}"));
58 }
59 out
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn the_nist_vector_for_abc() {
68 let mut hasher = Hasher::new();
69 hasher.update(b"abc");
70 assert_eq!(
71 hasher.finish(),
72 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
73 );
74 }
75
76 #[test]
77 fn the_empty_input_vector() {
78 assert_eq!(
79 Hasher::new().finish(),
80 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
81 );
82 }
83
84 #[test]
85 fn incremental_updates_match_a_single_update() {
86 let mut split = Hasher::new();
87 split.update(b"ab");
88 split.update(b"c");
89
90 let mut whole = Hasher::new();
91 whole.update(b"abc");
92
93 assert_eq!(split.finish(), whole.finish());
94 }
95
96 #[test]
97 fn hashing_a_file_matches_hashing_its_bytes() {
98 let dir = tempfile::tempdir().expect("tempdir");
99 let path = dir.path().join("f.bin");
100 std::fs::write(&path, b"abc").expect("write");
101 assert_eq!(
102 hash_file(&path).expect("hash"),
103 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
104 );
105 }
106}