volaris_tools/
hash.rs

1//! This provides functionality for hashing a file with `BLAKE3`, using a stream reader to keep memory usage low.
2
3use corecrypto::primitives::BLOCK_SIZE;
4use std::fmt;
5use std::{
6    cell::RefCell,
7    io::{Read, Seek},
8};
9
10use crate::hasher::Hasher;
11
12#[derive(Debug)]
13pub enum Error {
14    ResetCursorPosition,
15    ReadData,
16}
17
18impl fmt::Display for Error {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        match self {
21            Error::ResetCursorPosition => f.write_str("Unable to reset cursor position"),
22            Error::ReadData => f.write_str("Unable to read data"),
23        }
24    }
25}
26
27impl std::error::Error for Error {}
28
29pub struct Request<R: Read + Seek> {
30    pub reader: RefCell<R>,
31}
32
33pub fn execute<R: Read + Seek>(mut hasher: impl Hasher, req: Request<R>) -> Result<String, Error> {
34    req.reader
35        .borrow_mut()
36        .rewind()
37        .map_err(|_| Error::ResetCursorPosition)?;
38
39    let mut buffer = vec![0u8; BLOCK_SIZE].into_boxed_slice();
40
41    loop {
42        let read_count = req
43            .reader
44            .borrow_mut()
45            .read(&mut buffer)
46            .map_err(|_| Error::ReadData)?;
47        hasher.write(&buffer[..read_count]);
48        if read_count != BLOCK_SIZE {
49            break;
50        }
51    }
52
53    Ok(hasher.finish())
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use crate::hasher::Blake3Hasher;
60    use rand::RngCore;
61    use std::io::Cursor;
62
63    #[test]
64    fn should_hash_string() {
65        let text = "Hello world";
66        let mut bytes = text.as_bytes();
67        let reader = Cursor::new(&mut bytes);
68
69        let req = Request {
70            reader: RefCell::new(reader),
71        };
72
73        match execute(Blake3Hasher::default(), req) {
74            Err(_) => unreachable!(),
75            Ok(hash) => {
76                assert_eq!(hash, blake3::hash(text.as_bytes()).to_hex().to_string());
77            }
78        }
79    }
80
81    #[test]
82    fn should_hash_big_string() {
83        #[allow(
84            clippy::cast_sign_loss,
85            clippy::cast_possible_truncation,
86            clippy::cast_precision_loss
87        )]
88        let capacity = (BLOCK_SIZE as f32 * 1.5) as usize;
89        let mut buf = Vec::with_capacity(capacity);
90        rand::thread_rng().fill_bytes(&mut buf);
91
92        let orig_buf = buf.clone();
93        let reader = Cursor::new(&mut buf);
94
95        let req = Request {
96            reader: RefCell::new(reader),
97        };
98
99        match execute(Blake3Hasher::default(), req) {
100            Err(_) => unreachable!(),
101            Ok(hash) => {
102                assert_eq!(hash, blake3::hash(&orig_buf).to_hex().to_string());
103            }
104        }
105    }
106
107    #[test]
108    fn should_reset_position_and_make_hash() {
109        let text = "Hello world";
110        let mut bytes = text.as_bytes();
111        let mut reader = Cursor::new(&mut bytes);
112
113        reader.seek(std::io::SeekFrom::End(0)).unwrap();
114
115        let req = Request {
116            reader: RefCell::new(reader),
117        };
118
119        match execute(Blake3Hasher::default(), req) {
120            Err(_) => unreachable!(),
121            Ok(hash) => {
122                assert_eq!(hash, blake3::hash(text.as_bytes()).to_hex().to_string());
123            }
124        }
125    }
126}