Skip to main content

qubit_io/wrappers/
checksum_reader.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8use std::hash::Hasher;
9use std::io::{
10    Read,
11    Result,
12    Seek,
13    SeekFrom,
14};
15
16/// Reader wrapper that updates a checksum hasher with bytes read.
17///
18/// `ChecksumReader` forwards reads to the wrapped reader and writes every
19/// successfully read byte into the wrapped [`Hasher`]. Failed reads do not
20/// update the hasher.
21///
22/// The checksum value is whatever the supplied [`Hasher`] reports. The Rust
23/// standard-library hashers are not specified as stable file formats and are
24/// not cryptographic digests; use this wrapper for stream instrumentation
25/// unless the chosen hasher explicitly documents stronger guarantees.
26///
27/// Seeking changes only the wrapped reader position. It does not rewind,
28/// subtract from, or otherwise adjust the hasher state.
29///
30/// # Examples
31/// ```
32/// use std::collections::hash_map::DefaultHasher;
33/// use std::hash::Hasher;
34/// use std::io::{
35///     Cursor,
36///     Read,
37/// };
38///
39/// use qubit_io::ChecksumReader;
40///
41/// let mut expected = DefaultHasher::new();
42/// expected.write(b"payload");
43///
44/// let mut reader = ChecksumReader::new(Cursor::new(b"payload"), DefaultHasher::new());
45/// let mut data = Vec::new();
46/// reader.read_to_end(&mut data)?;
47///
48/// assert_eq!(b"payload", data.as_slice());
49/// assert_eq!(expected.finish(), reader.checksum());
50/// # Ok::<(), std::io::Error>(())
51/// ```
52pub struct ChecksumReader<R, H> {
53    inner: R,
54    hasher: H,
55}
56
57impl<R, H> ChecksumReader<R, H>
58where
59    H: Hasher,
60{
61    /// Creates a checksum reader.
62    ///
63    /// # Parameters
64    /// - `inner`: Reader to wrap.
65    /// - `hasher`: Hasher updated with successfully read bytes.
66    ///
67    /// # Returns
68    /// A new checksum reader.
69    #[inline]
70    pub fn new(inner: R, hasher: H) -> Self {
71        Self { inner, hasher }
72    }
73
74    /// Returns the current checksum value.
75    ///
76    /// # Returns
77    /// The value reported by [`Hasher::finish`].
78    #[inline]
79    pub fn checksum(&self) -> u64 {
80        self.hasher.finish()
81    }
82
83    /// Returns an immutable reference to the wrapped reader.
84    ///
85    /// # Returns
86    /// The wrapped reader reference.
87    #[inline]
88    pub fn get_ref(&self) -> &R {
89        &self.inner
90    }
91
92    /// Returns a mutable reference to the wrapped reader.
93    ///
94    /// # Returns
95    /// The wrapped reader reference.
96    #[inline]
97    pub fn get_mut(&mut self) -> &mut R {
98        &mut self.inner
99    }
100
101    /// Returns an immutable reference to the wrapped hasher.
102    ///
103    /// # Returns
104    /// The wrapped hasher reference.
105    #[inline]
106    pub fn hasher_ref(&self) -> &H {
107        &self.hasher
108    }
109
110    /// Returns a mutable reference to the wrapped hasher.
111    ///
112    /// # Returns
113    /// The wrapped hasher reference.
114    #[inline]
115    pub fn hasher_mut(&mut self) -> &mut H {
116        &mut self.hasher
117    }
118
119    /// Consumes this wrapper and returns the wrapped reader and hasher.
120    ///
121    /// # Returns
122    /// A tuple containing the wrapped reader and hasher.
123    #[inline]
124    pub fn into_inner(self) -> (R, H) {
125        (self.inner, self.hasher)
126    }
127}
128
129impl<R, H> Read for ChecksumReader<R, H>
130where
131    R: Read,
132    H: Hasher,
133{
134    fn read(&mut self, buffer: &mut [u8]) -> Result<usize> {
135        let count = self.inner.read(buffer)?;
136        self.hasher.write(&buffer[..count]);
137        Ok(count)
138    }
139}
140
141impl<R, H> Seek for ChecksumReader<R, H>
142where
143    R: Seek,
144    H: Hasher,
145{
146    /// Seeks the wrapped reader without changing the hasher state.
147    ///
148    /// # Parameters
149    /// - `position`: Target reader position.
150    ///
151    /// # Returns
152    /// The new reader position.
153    ///
154    /// # Errors
155    /// Returns the seek error reported by the wrapped reader.
156    #[inline]
157    fn seek(&mut self, position: SeekFrom) -> Result<u64> {
158        self.inner.seek(position)
159    }
160}