Skip to main content

git_internal/internal/pack/
wrapper.rs

1//! Reader wrapper that tracks how many bytes of a pack have been consumed while keeping a running
2//! SHA-1/SHA-256 hash for trailer verification.
3
4use std::io::{self, BufRead, Read};
5
6use sha1::{Digest, Sha1};
7
8use crate::{
9    hash::{HashKind, ObjectHash, get_hash_kind},
10    utils::HashAlgorithm,
11};
12/// [`Wrapper`] is a wrapper around a reader that also computes the SHA1/ SHA256 hash of the data read.
13///
14/// It is designed to work with any reader that implements `BufRead`.
15///
16/// Fields:
17/// * `inner`: The inner reader.
18/// * `hash`: The  hash state.
19/// * `count_hash`: A flag to indicate whether to compute the hash while reading.
20///
21pub struct Wrapper<R> {
22    inner: R,
23    hash: HashAlgorithm,
24    bytes_read: usize,
25}
26
27impl<R> Wrapper<R>
28where
29    R: BufRead,
30{
31    /// Constructs a new [`Wrapper`] with the given reader and a flag to enable or disable hashing.
32    ///
33    /// # Parameters
34    /// * `inner`: The reader to wrap.
35    /// * `count_hash`: If `true`, the hash is computed while reading; otherwise, it is not.
36    pub fn new(inner: R) -> Self {
37        Self {
38            inner,
39            hash: match get_hash_kind() {
40                HashKind::Sha1 => HashAlgorithm::Sha1(Sha1::new()),
41                HashKind::Sha256 => HashAlgorithm::Sha256(sha2::Sha256::new()),
42            }, // Initialize a new SHA1/ SHA256 hasher
43            bytes_read: 0,
44        }
45    }
46
47    /// Returns the number of bytes read so far.
48    pub fn bytes_read(&self) -> usize {
49        self.bytes_read
50    }
51
52    /// Returns the final SHA1/ SHA256 hash of the data read so far.
53    ///
54    /// This is a clone of the internal hash state finalized into a SHA1/ SHA256 hash.
55    pub fn final_hash(&self) -> ObjectHash {
56        match &self.hash.clone() {
57            HashAlgorithm::Sha1(hasher) => {
58                let re: [u8; 20] = hasher.clone().finalize().into(); // Clone, finalize, and convert the hash into bytes
59                ObjectHash::from_bytes(&re).unwrap()
60            }
61            HashAlgorithm::Sha256(hasher) => {
62                let re: [u8; 32] = hasher.clone().finalize().into(); // Clone, finalize, and convert the hash into bytes
63                ObjectHash::from_bytes(&re).unwrap()
64            }
65        }
66    }
67}
68
69impl<R> BufRead for Wrapper<R>
70where
71    R: BufRead,
72{
73    /// Provides access to the internal buffer of the wrapped reader without consuming it.
74    fn fill_buf(&mut self) -> io::Result<&[u8]> {
75        self.inner.fill_buf() // Delegate to the inner reader
76    }
77
78    /// Consumes data from the buffer and updates the hash if `count_hash` is true.
79    ///
80    /// # Parameters
81    /// * `amt`: The amount of data to consume from the buffer.
82    fn consume(&mut self, amt: usize) {
83        let buffer = self.inner.fill_buf().expect("Failed to fill buffer");
84        match &mut self.hash {
85            HashAlgorithm::Sha1(hasher) => hasher.update(&buffer[..amt]), // Update SHA1 hash with the data being consumed
86            HashAlgorithm::Sha256(hasher) => hasher.update(&buffer[..amt]), // Update SHA256 hash with the data being consumed
87        }
88        self.inner.consume(amt); // Consume the data from the inner reader
89        self.bytes_read += amt;
90    }
91}
92
93impl<R> Read for Wrapper<R>
94where
95    R: BufRead,
96{
97    /// Reads data into the provided buffer and updates the hash if `count_hash` is true.
98    /// <br> [Read::read_exact] calls it internally.
99    ///
100    /// # Parameters
101    /// * `buf`: The buffer to read data into.
102    ///
103    /// # Returns
104    /// Returns the number of bytes read.
105    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
106        let o = self.inner.read(buf)?; // Read data into the buffer
107        match &mut self.hash {
108            HashAlgorithm::Sha1(hasher) => hasher.update(&buf[..o]), // Update SHA1 hash with the data being read
109            HashAlgorithm::Sha256(hasher) => hasher.update(&buf[..o]), // Update SHA256 hash with the data being read
110        }
111        self.bytes_read += o;
112        Ok(o) // Return the number of bytes read
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use std::io::{self, BufReader, Cursor, Read};
119
120    use sha1::{Digest, Sha1};
121
122    use crate::{
123        hash::{HashKind, ObjectHash, set_hash_kind_for_test},
124        internal::pack::wrapper::Wrapper,
125    };
126
127    /// Helper function to test wrapper read functionality for different hash kinds.
128    fn wrapper_read(kind: HashKind) {
129        let _guard = set_hash_kind_for_test(kind);
130        let data = b"Hello, world!"; // Sample data
131        let cursor = Cursor::new(data.as_ref());
132        let buf_reader = BufReader::new(cursor);
133        let mut wrapper = Wrapper::new(buf_reader);
134
135        let mut buffer = vec![0; data.len()];
136        wrapper.read_exact(&mut buffer).unwrap();
137
138        assert_eq!(buffer, data);
139    }
140
141    /// Verify Wrapper correctly reads data for both SHA-1 and SHA-256 hash modes.
142    #[test]
143    fn test_wrapper_read() {
144        wrapper_read(HashKind::Sha1);
145        wrapper_read(HashKind::Sha256);
146    }
147
148    /// Helper function to test wrapper hash functionality for different hash kinds.
149    fn wrapper_hash_with_kind(kind: HashKind) -> io::Result<()> {
150        let _guard = set_hash_kind_for_test(kind);
151        let data = b"Hello, world!";
152        let cursor = Cursor::new(data.as_ref());
153        let buf_reader = BufReader::new(cursor);
154        let mut wrapper = Wrapper::new(buf_reader);
155
156        let mut buffer = vec![0; data.len()];
157        wrapper.read_exact(&mut buffer)?;
158
159        let hash_result = wrapper.final_hash();
160        let expected_hash = match kind {
161            HashKind::Sha1 => ObjectHash::from_bytes(&Sha1::digest(data)).unwrap(),
162            HashKind::Sha256 => ObjectHash::from_bytes(&sha2::Sha256::digest(data)).unwrap(),
163        };
164
165        assert_eq!(hash_result, expected_hash);
166        Ok(())
167    }
168    #[test]
169    fn test_wrapper_hash() -> io::Result<()> {
170        wrapper_hash_with_kind(HashKind::Sha1)?;
171        wrapper_hash_with_kind(HashKind::Sha256)?;
172        Ok(())
173    }
174}