1use 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}