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}