Skip to main content

qubit_io/wrappers/
checksum_reader.rs

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