chksum_reader/
lib.rs

1//! This crate provides a convenient interface for calculating hash digests on the fly while reading data from a reader. It supports various hash algorithms, and the library is designed to be easy to use.
2//!
3//! # Setup
4//!
5//! To use this crate, add the following entry to your `Cargo.toml` file in the `dependencies` section:
6//!
7//! ```toml
8//! [dependencies]
9//! chksum-reader = "0.1.0"
10//! ```
11//!
12//! Alternatively, you can use the [`cargo add`](https://doc.rust-lang.org/cargo/commands/cargo-add.html) subcommand:
13//!
14//! ```sh
15//! cargo add chksum-reader
16//! ```     
17//!
18//! # Features
19//!
20//! ## Asynchronous Runtime
21//!
22//! * `async-runtime-tokio`: Enables async interface for Tokio runtime.
23//!
24//! By default, neither of these features is enabled.
25//!
26//! # Usage
27//!
28//! ```rust,ignore
29//! use std::io::{self, Read};
30//!
31//! use chksum_md5::MD5;
32//! use chksum_reader::Reader;
33//!
34//! fn main() -> io::Result<()> {
35//!     // Create a new reader with the MD5 hash algorithm
36//!     let mut reader = Reader::<_, MD5>::new(io::stdin());
37//!
38//!     // Read data from the reader
39//!     let mut buffer = Vec::new();
40//!     reader.read_to_end(&mut buffer)?;
41//!
42//!     // Get the calculated digest
43//!     let digest = reader.digest();
44//!
45//!     // Print the digest (hex representation)
46//!     println!("Digest: {}", digest.to_hex_lowercase());
47//!
48//!     Ok(())
49//! }
50//! ```
51//!
52//! # Implementations
53//!
54//! This crate should be used along with a hash implementation crate.
55//!  
56//! Various crates implement their own [`Reader`], which can be enabled with the `reader` Cargo feature.
57//!
58//! # License
59//!
60//! This crate is licensed under the MIT License.
61
62#![cfg_attr(docsrs, feature(doc_auto_cfg))]
63#![forbid(unsafe_code)]
64
65use std::io::{self, BufRead, Read};
66#[cfg(feature = "async-runtime-tokio")]
67use std::pin::{pin, Pin};
68#[cfg(feature = "async-runtime-tokio")]
69use std::task::{Context, Poll};
70
71use chksum_core::Hash;
72#[cfg(feature = "async-runtime-tokio")]
73use tokio::io::{AsyncRead, AsyncReadExt, ReadBuf};
74
75/// Creates new [`Reader`].
76pub fn new<R, H>(inner: R) -> Reader<R, H>
77where
78    R: Read,
79    H: Hash,
80{
81    Reader::new(inner)
82}
83
84/// Creates new [`Reader`] with provided hash.
85pub fn with_hash<R, H>(inner: R, hash: H) -> Reader<R, H>
86where
87    R: Read,
88    H: Hash,
89{
90    Reader::with_hash(inner, hash)
91}
92
93#[cfg(feature = "async-runtime-tokio")]
94/// Creates new [`AsyncReader`].
95pub fn async_new<R, H>(inner: R) -> AsyncReader<R, H>
96where
97    R: AsyncReadExt,
98    H: Hash,
99{
100    AsyncReader::new(inner)
101}
102
103#[cfg(feature = "async-runtime-tokio")]
104/// Creates new [`AsyncReader`] with provided hash.
105pub fn async_with_hash<R, H>(inner: R, hash: H) -> AsyncReader<R, H>
106where
107    R: AsyncReadExt,
108    H: Hash,
109{
110    AsyncReader::with_hash(inner, hash)
111}
112
113/// Wraps a reader and calculates the hash digest on the fly.
114#[derive(Clone, Debug, PartialEq, Eq)]
115pub struct Reader<R, H>
116where
117    R: Read,
118    H: Hash,
119{
120    inner: R,
121    hash: H,
122}
123
124impl<R, H> Reader<R, H>
125where
126    R: Read,
127    H: Hash,
128{
129    /// Creates new [`Reader`].
130    pub fn new(inner: R) -> Self {
131        let hash = H::default();
132        Self::with_hash(inner, hash)
133    }
134
135    /// Creates new [`Reader`] with provided hash.
136    #[must_use]
137    pub const fn with_hash(inner: R, hash: H) -> Self {
138        Self { inner, hash }
139    }
140
141    /// Unwraps this [`Reader`], returning the underlying reader.
142    #[must_use]
143    pub fn into_inner(self) -> R {
144        let Self { inner, .. } = self;
145        inner
146    }
147
148    /// Returns calculated hash digest.
149    #[must_use]
150    pub fn digest(&self) -> H::Digest {
151        self.hash.digest()
152    }
153}
154
155impl<R, H> Read for Reader<R, H>
156where
157    R: Read,
158    H: Hash,
159{
160    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
161        let n = self.inner.read(buf)?;
162        self.hash.update(&buf[..n]);
163        Ok(n)
164    }
165}
166
167impl<R, H> BufRead for Reader<R, H>
168where
169    R: BufRead,
170    H: Hash,
171{
172    fn consume(&mut self, amt: usize) {
173        self.inner.consume(amt);
174    }
175
176    fn fill_buf(&mut self) -> io::Result<&[u8]> {
177        self.inner.fill_buf()
178    }
179}
180
181/// Wraps a reader and calculates the hash digest on the fly.
182#[cfg(feature = "async-runtime-tokio")]
183#[derive(Clone, Debug, PartialEq, Eq)]
184pub struct AsyncReader<R, H>
185where
186    R: AsyncReadExt,
187    H: Hash,
188{
189    inner: R,
190    hash: H,
191}
192
193#[cfg(feature = "async-runtime-tokio")]
194impl<R, H> AsyncReader<R, H>
195where
196    R: AsyncReadExt,
197    H: Hash,
198{
199    /// Creates new [`AsyncReader`].
200    pub fn new(inner: R) -> Self {
201        let hash = H::default();
202        Self::with_hash(inner, hash)
203    }
204
205    /// Creates new [`AsyncReader`] with provided hash.
206    #[must_use]
207    pub const fn with_hash(inner: R, hash: H) -> Self {
208        Self { inner, hash }
209    }
210
211    /// Unwraps this [`AsyncReader`], returning the underlying reader.
212    #[must_use]
213    pub fn into_inner(self) -> R {
214        let Self { inner, .. } = self;
215        inner
216    }
217
218    /// Returns calculated hash digest.
219    #[must_use]
220    pub fn digest(&self) -> H::Digest {
221        self.hash.digest()
222    }
223}
224
225#[cfg(feature = "async-runtime-tokio")]
226impl<R, H> AsyncRead for AsyncReader<R, H>
227where
228    R: AsyncRead + Unpin,
229    H: Hash + Unpin,
230{
231    fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>> {
232        let Self { inner, hash } = self.get_mut();
233        match pin!(inner).poll_read(cx, buf) {
234            Poll::Ready(Ok(())) => {
235                hash.update(buf.filled());
236                Poll::Ready(Ok(()))
237            },
238            poll => poll,
239        }
240    }
241}