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}