1use crate::SigmfError;
8use sha2::Digest;
9use std::io::Read;
10
11pub fn hash_from_reader(reader: &mut dyn Read) -> Result<String, SigmfError> {
20 let mut chunk = [0u8; 64 * 1024];
21
22 let mut hasher = sha2::Sha512::new();
23 loop {
24 match reader.read(&mut chunk) {
25 Ok(0) => break, Ok(n) => hasher.update(&chunk[..n]),
27 Err(error) if error.kind() == std::io::ErrorKind::Interrupted => continue,
28 Err(err) => return Err(SigmfError::Io(err)),
29 }
30 }
31 let hash = hasher.finalize();
32 let hash_str = hash.iter().map(|byte| format!("{:02x}", byte)).collect();
33 Ok(hash_str)
34}
35
36pub fn hash_from_path(path: impl AsRef<std::path::Path>) -> Result<String, SigmfError> {
42 let file = std::fs::File::open(path)?;
43 let mut reader = std::io::BufReader::new(file);
44 let hash = hash_from_reader(&mut reader)?;
45 Ok(hash)
46}
47
48pub fn hash_from_slice(data: &[u8]) -> Result<String, SigmfError> {
69 let mut hasher = sha2::Sha512::new();
70 hasher.update(data);
71 let hash = hasher.finalize();
72 let hash_str = hash.iter().map(|byte| format!("{:02x}", byte)).collect();
73 Ok(hash_str)
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use std::io::{self, Cursor};
80
81 struct InterruptedShortReader {
82 cursor: Cursor<Vec<u8>>,
83 interrupted: bool,
84 }
85
86 impl Read for InterruptedShortReader {
87 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
88 if !self.interrupted {
89 self.interrupted = true;
90 return Err(io::ErrorKind::Interrupted.into());
91 }
92 let limit = buffer.len().min(3);
93 self.cursor.read(&mut buffer[..limit])
94 }
95 }
96
97 #[test]
98 fn hashes_a_self_contained_temporary_fixture() {
99 let path = std::env::temp_dir().join(format!(
100 "sigmf-hash-test-{}-{}",
101 std::process::id(),
102 std::time::SystemTime::now()
103 .duration_since(std::time::UNIX_EPOCH)
104 .unwrap()
105 .as_nanos()
106 ));
107 let fixture = b"sigmf hash fixture";
108 std::fs::write(&path, fixture).unwrap();
109 let hash = hash_from_path(&path).unwrap();
110 std::fs::remove_file(path).unwrap();
111 assert_eq!(hash, hash_from_slice(fixture).unwrap());
112 }
113
114 #[test]
115 fn reader_hash_retries_interrupted_and_short_reads() {
116 let fixture = b"short and interrupted";
117 let mut reader = InterruptedShortReader {
118 cursor: Cursor::new(fixture.to_vec()),
119 interrupted: false,
120 };
121 assert_eq!(
122 hash_from_reader(&mut reader).unwrap(),
123 hash_from_slice(fixture).unwrap()
124 );
125 }
126}