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 optional hash state. `None` means only bytes read are counted.
19/// * `bytes_read`: The number of bytes consumed from the wrapped reader.
20///
21pub struct Wrapper<R> {
22    inner: R,
23    hash: Option<HashAlgorithm>,
24    bytes_read: usize,
25}
26
27impl<R> Wrapper<R>
28where
29    R: BufRead,
30{
31    /// Constructs a new [`Wrapper`] with hash tracking enabled.
32    ///
33    /// # Parameters
34    /// * `inner`: The reader to wrap.
35    pub fn new(inner: R) -> Self {
36        Self {
37            inner,
38            hash: Some(match get_hash_kind() {
39                HashKind::Sha1 => HashAlgorithm::Sha1(Sha1::new()),
40                HashKind::Sha256 => HashAlgorithm::Sha256(sha2::Sha256::new()),
41            }), // Initialize a new SHA1/ SHA256 hasher
42            bytes_read: 0,
43        }
44    }
45
46    /// Constructs a wrapper that only tracks bytes read, skipping the running hash.
47    pub fn new_without_hash(inner: R) -> Self {
48        Self {
49            inner,
50            hash: None,
51            bytes_read: 0,
52        }
53    }
54
55    /// Returns the number of bytes read so far.
56    pub fn bytes_read(&self) -> usize {
57        self.bytes_read
58    }
59
60    /// Returns the final SHA1/ SHA256 hash of the data read so far.
61    ///
62    /// This is a clone of the internal hash state finalized into a SHA1/ SHA256 hash.
63    pub fn final_hash(&self) -> ObjectHash {
64        match &self
65            .hash
66            .clone()
67            .expect("Wrapper::final_hash called while hash tracking is disabled")
68        {
69            HashAlgorithm::Sha1(hasher) => {
70                let re: [u8; 20] = hasher.clone().finalize().into(); // Clone, finalize, and convert the hash into bytes
71                ObjectHash::from_bytes(&re).unwrap()
72            }
73            HashAlgorithm::Sha256(hasher) => {
74                let re: [u8; 32] = hasher.clone().finalize().into(); // Clone, finalize, and convert the hash into bytes
75                ObjectHash::from_bytes(&re).unwrap()
76            }
77        }
78    }
79}
80
81impl<R> BufRead for Wrapper<R>
82where
83    R: BufRead,
84{
85    /// Provides access to the internal buffer of the wrapped reader without consuming it.
86    fn fill_buf(&mut self) -> io::Result<&[u8]> {
87        self.inner.fill_buf() // Delegate to the inner reader
88    }
89
90    /// Consumes data from the buffer and updates the hash when tracking is enabled.
91    ///
92    /// # Parameters
93    /// * `amt`: The amount of data to consume from the buffer.
94    fn consume(&mut self, amt: usize) {
95        let buffer = self.inner.fill_buf().expect("Failed to fill buffer");
96        if let Some(hash) = &mut self.hash {
97            match hash {
98                HashAlgorithm::Sha1(hasher) => hasher.update(&buffer[..amt]), // Update SHA1 hash with the data being consumed
99                HashAlgorithm::Sha256(hasher) => hasher.update(&buffer[..amt]), // Update SHA256 hash with the data being consumed
100            }
101        }
102        self.inner.consume(amt); // Consume the data from the inner reader
103        self.bytes_read += amt;
104    }
105}
106
107impl<R> Read for Wrapper<R>
108where
109    R: BufRead,
110{
111    /// Reads data into the provided buffer and updates the hash when tracking is enabled.
112    /// <br> [Read::read_exact] calls it internally.
113    ///
114    /// # Parameters
115    /// * `buf`: The buffer to read data into.
116    ///
117    /// # Returns
118    /// Returns the number of bytes read.
119    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
120        let o = self.inner.read(buf)?; // Read data into the buffer
121        if let Some(hash) = &mut self.hash {
122            match hash {
123                HashAlgorithm::Sha1(hasher) => hasher.update(&buf[..o]), // Update SHA1 hash with the data being read
124                HashAlgorithm::Sha256(hasher) => hasher.update(&buf[..o]), // Update SHA256 hash with the data being read
125            }
126        }
127        self.bytes_read += o;
128        Ok(o) // Return the number of bytes read
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use std::io::{self, BufReader, Cursor, Read};
135
136    use sha1::{Digest, Sha1};
137
138    use crate::{
139        hash::{HashKind, ObjectHash, set_hash_kind_for_test},
140        internal::pack::wrapper::Wrapper,
141    };
142
143    /// Helper function to test wrapper read functionality for different hash kinds.
144    fn wrapper_read(kind: HashKind) {
145        let _guard = set_hash_kind_for_test(kind);
146        let data = b"Hello, world!"; // Sample data
147        let cursor = Cursor::new(data.as_ref());
148        let buf_reader = BufReader::new(cursor);
149        let mut wrapper = Wrapper::new(buf_reader);
150
151        let mut buffer = vec![0; data.len()];
152        wrapper.read_exact(&mut buffer).unwrap();
153
154        assert_eq!(buffer, data);
155    }
156
157    /// Verify Wrapper correctly reads data for both SHA-1 and SHA-256 hash modes.
158    #[test]
159    fn test_wrapper_read() {
160        wrapper_read(HashKind::Sha1);
161        wrapper_read(HashKind::Sha256);
162    }
163
164    /// Helper function to test wrapper hash functionality for different hash kinds.
165    fn wrapper_hash_with_kind(kind: HashKind) -> io::Result<()> {
166        let _guard = set_hash_kind_for_test(kind);
167        let data = b"Hello, world!";
168        let cursor = Cursor::new(data.as_ref());
169        let buf_reader = BufReader::new(cursor);
170        let mut wrapper = Wrapper::new(buf_reader);
171
172        let mut buffer = vec![0; data.len()];
173        wrapper.read_exact(&mut buffer)?;
174
175        let hash_result = wrapper.final_hash();
176        let expected_hash = match kind {
177            HashKind::Sha1 => ObjectHash::from_bytes(&Sha1::digest(data)).unwrap(),
178            HashKind::Sha256 => ObjectHash::from_bytes(&sha2::Sha256::digest(data)).unwrap(),
179        };
180
181        assert_eq!(hash_result, expected_hash);
182        Ok(())
183    }
184    #[test]
185    fn test_wrapper_hash() -> io::Result<()> {
186        wrapper_hash_with_kind(HashKind::Sha1)?;
187        wrapper_hash_with_kind(HashKind::Sha256)?;
188        Ok(())
189    }
190}