chksum_core/
lib.rs

1//! Core traits and functions for straightforward hash computation of bytes, files, directories and more.
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-core = "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-core
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//! # Example Crates
27//!
28//! For implementation-specific examples, refer to the source code of the following crates:
29//!
30//! * [`chksum-md5`](https://docs.rs/chksum-md5/)
31//! * [`chksum-sha1`](https://docs.rs/chksum-sha1/)
32//! * [`chksum-sha2`](https://docs.rs/chksum-sha2/)
33//!     * [`chksum-sha2-224`](https://docs.rs/chksum-sha2-224/)
34//!     * [`chksum-sha2-256`](https://docs.rs/chksum-sha2-256/)
35//!     * [`chksum-sha2-384`](https://docs.rs/chksum-sha2-384/)
36//!     * [`chksum-sha2-512`](https://docs.rs/chksum-sha2-512/)
37//!
38//! # License
39//!
40//! This crate is licensed under the MIT License.
41
42#![cfg_attr(docsrs, feature(doc_auto_cfg))]
43#![forbid(unsafe_code)]
44
45mod error;
46#[cfg(feature = "async-runtime-tokio")]
47mod tokio;
48
49use std::fmt::{Display, LowerHex, UpperHex};
50use std::fs::{read_dir, DirEntry, File, ReadDir};
51use std::io::{self, BufRead, BufReader, IsTerminal, Stdin, StdinLock};
52use std::path::{Path, PathBuf};
53
54#[cfg(feature = "async-runtime-tokio")]
55use async_trait::async_trait;
56#[doc(no_inline)]
57pub use chksum_hash_core as hash;
58
59pub use crate::error::{Error, Result};
60
61/// Creates a default hash.
62#[must_use]
63pub fn default<H>() -> H
64where
65    H: Hash,
66{
67    Default::default()
68}
69
70/// Computes the hash of the given input.
71pub fn hash<T>(data: impl Hashable) -> T::Digest
72where
73    T: Hash,
74{
75    data.hash::<T>()
76}
77
78/// Computes the hash of the given input.
79pub fn chksum<T>(mut data: impl Chksumable) -> Result<T::Digest>
80where
81    T: Hash,
82{
83    data.chksum::<T>()
84}
85
86/// Computes the hash of the given input.
87#[cfg(feature = "async-runtime-tokio")]
88pub async fn async_chksum<T>(mut data: impl AsyncChksumable) -> Result<T::Digest>
89where
90    T: Hash + Send,
91{
92    data.chksum::<T>().await
93}
94
95/// A trait for hash digests.
96pub trait Digest: Display {
97    /// Returns a byte slice of the digest's contents.
98    #[must_use]
99    fn as_bytes(&self) -> &[u8]
100    where
101        Self: AsRef<[u8]>,
102    {
103        self.as_ref()
104    }
105
106    /// Returns a string in the lowercase hexadecimal representation.
107    #[must_use]
108    fn to_hex_lowercase(&self) -> String
109    where
110        Self: LowerHex,
111    {
112        format!("{self:x}")
113    }
114
115    /// Returns a string in the uppercase hexadecimal representation.
116    #[must_use]
117    fn to_hex_uppercase(&self) -> String
118    where
119        Self: UpperHex,
120    {
121        format!("{self:X}")
122    }
123}
124
125/// A trait for hash objects.
126pub trait Hash: Default {
127    /// The type representing the digest produced by finalizing the hash.
128    type Digest: Digest;
129
130    /// Calculates the hash digest of an input data.
131    #[must_use]
132    fn hash<T>(data: T) -> Self::Digest
133    where
134        T: AsRef<[u8]>,
135    {
136        let mut hash = Self::default();
137        hash.update(data);
138        hash.digest()
139    }
140
141    /// Updates the hash state with an input data.
142    fn update<T>(&mut self, data: T)
143    where
144        T: AsRef<[u8]>;
145
146    /// Resets the hash state to its initial state.
147    fn reset(&mut self);
148
149    /// Produces the hash digest.
150    #[must_use]
151    fn digest(&self) -> Self::Digest;
152}
153
154/// A trait for simple bytes-like objects.
155pub trait Hashable: AsRef<[u8]> {
156    /// Computes the hash digest.
157    fn hash<H>(&self) -> H::Digest
158    where
159        H: Hash,
160    {
161        let mut hash = H::default();
162        self.hash_with(&mut hash);
163        hash.digest()
164    }
165
166    /// Updates the given hash instance with the bytes from this object.
167    fn hash_with<H>(&self, hash: &mut H)
168    where
169        H: Hash,
170    {
171        hash.update(self);
172    }
173}
174
175macro_rules! impl_hashable {
176    ([$t:ty; LENGTH], $($rest:tt)+) => {
177        impl_hashable!([$t; LENGTH]);
178        impl_hashable!($($rest)*);
179    };
180
181    ($t:ty, $($rest:tt)+) => {
182        impl_hashable!($t);
183        impl_hashable!($($rest)*);
184    };
185
186    ([$t:ty; LENGTH]) => {
187        impl<const LENGTH: usize> Hashable for [$t; LENGTH] {}
188    };
189
190    ($t:ty) => {
191        impl Hashable for $t {}
192    };
193}
194
195impl_hashable!(&[u8], [u8; LENGTH], Vec<u8>, &str, String);
196
197impl<T> Hashable for &T where T: Hashable {}
198
199impl<T> Hashable for &mut T where T: Hashable {}
200
201/// A trait for complex objects which must be processed chunk by chunk.
202pub trait Chksumable {
203    /// Calculates the checksum of the object.
204    fn chksum<H>(&mut self) -> Result<H::Digest>
205    where
206        H: Hash,
207    {
208        let mut hash = H::default();
209        self.chksum_with(&mut hash)?;
210        Ok(hash.digest())
211    }
212
213    /// Updates the given hash instance with the data from the object.
214    fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
215    where
216        H: Hash;
217}
218
219impl<T> Chksumable for T
220where
221    T: Hashable,
222{
223    fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
224    where
225        H: Hash,
226    {
227        self.hash_with(hash);
228        Ok(())
229    }
230}
231
232macro_rules! impl_chksumable {
233    ($($t:ty),+ => $i:tt) => {
234        $(
235            impl Chksumable for $t $i
236        )*
237    };
238}
239
240impl_chksumable!(Path, &Path, &mut Path => {
241    fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
242    where
243        H: Hash,
244    {
245        let metadata = self.metadata()?;
246        if metadata.is_dir() {
247            read_dir(self)?.chksum_with(hash)
248        } else {
249            // everything treat as a file when it is not a directory
250            File::open(self)?.chksum_with(hash)
251        }
252    }
253});
254
255impl_chksumable!(PathBuf, &PathBuf, &mut PathBuf => {
256    fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
257    where
258        H: Hash,
259    {
260        Chksumable::chksum_with(&mut self.as_path(), hash)
261    }
262});
263
264impl_chksumable!(File, &File, &mut File => {
265    fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
266    where
267        H: Hash,
268    {
269        if self.is_terminal() {
270            return Err(Error::IsTerminal);
271        }
272
273        let mut reader = BufReader::new(self);
274        loop {
275            let buffer = reader.fill_buf()?;
276            let length = buffer.len();
277            if length == 0 {
278                break;
279            }
280            buffer.hash_with(hash);
281            reader.consume(length);
282        }
283        Ok(())
284    }
285});
286
287impl_chksumable!(DirEntry, &DirEntry, &mut DirEntry => {
288    fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
289    where
290        H: Hash,
291    {
292        Chksumable::chksum_with(&mut self.path(), hash)
293    }
294});
295
296impl_chksumable!(ReadDir, &mut ReadDir => {
297    fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
298    where
299        H: Hash,
300    {
301        let dir_entries: io::Result<Vec<DirEntry>> = self.collect();
302        let mut dir_entries = dir_entries?;
303        dir_entries.sort_by_key(DirEntry::path);
304        dir_entries
305            .into_iter()
306            .try_for_each(|mut dir_entry| dir_entry.chksum_with(hash))?;
307        Ok(())
308    }
309});
310
311impl_chksumable!(Stdin, &Stdin, &mut Stdin => {
312    fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
313    where
314        H: Hash,
315    {
316        self.lock().chksum_with(hash)
317    }
318});
319
320impl_chksumable!(StdinLock<'_>, &mut StdinLock<'_> => {
321    fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
322    where
323        H: Hash,
324    {
325        if self.is_terminal() {
326            return Err(Error::IsTerminal);
327        }
328
329        loop {
330            let buffer = self.fill_buf()?;
331            let length = buffer.len();
332            if length == 0 {
333                break;
334            }
335            buffer.hash_with(hash);
336            self.consume(length);
337        }
338        Ok(())
339    }
340});
341
342/// A trait for complex objects which must be processed chunk by chunk.
343#[cfg(feature = "async-runtime-tokio")]
344#[async_trait]
345pub trait AsyncChksumable: Send {
346    /// Calculates the checksum of the object.
347    async fn chksum<H>(&mut self) -> Result<H::Digest>
348    where
349        H: Hash + Send,
350    {
351        let mut hash = H::default();
352        self.chksum_with(&mut hash).await?;
353        Ok(hash.digest())
354    }
355
356    /// Updates the given hash instance with the data from the object.
357    async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
358    where
359        H: Hash + Send;
360}
361
362#[cfg(feature = "async-runtime-tokio")]
363#[async_trait]
364impl<T> AsyncChksumable for T
365where
366    T: Hashable + Send,
367{
368    async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
369    where
370        H: Hash + Send,
371    {
372        self.hash_with(hash);
373        Ok(())
374    }
375}