shell_rs/
hashsum.rs

1// Copyright (c) 2021 Xu Shaohua <shaohua@biofan.org>. All rights reserved.
2// Use of this source is governed by Apache-2.0 License that can be found
3// in the LICENSE file.
4
5use sha2::Digest;
6use std::fs::File;
7use std::io::Read;
8use std::path::Path;
9
10use super::error::Error;
11
12#[inline]
13pub fn crc32<P: AsRef<Path>>(file: P) -> Result<String, Error> {
14    let mut reader = File::open(&file)?;
15    let mut hasher = crc32fast::Hasher::new();
16
17    let mut buffer = Vec::with_capacity(16 * 1024);
18    loop {
19        let n_read = reader.read_to_end(&mut buffer)?;
20        if n_read == 0 {
21            break;
22        }
23        hasher.update(&buffer[..n_read]);
24    }
25    let checksum = hasher.finalize();
26    Ok(checksum.to_string())
27}
28
29#[inline]
30pub fn b2sum<P: AsRef<Path>>(file: P, options: &Options) -> Result<String, Error> {
31    checksum(file, HashAlgo::B2, options)
32}
33
34#[inline]
35pub fn md5sum<P: AsRef<Path>>(file: P, options: &Options) -> Result<String, Error> {
36    checksum(file, HashAlgo::Md5, options)
37}
38
39#[inline]
40pub fn sha1sum<P: AsRef<Path>>(file: P, options: &Options) -> Result<String, Error> {
41    checksum(file, HashAlgo::Sha1, options)
42}
43
44#[inline]
45pub fn sha224sum<P: AsRef<Path>>(file: P, options: &Options) -> Result<String, Error> {
46    checksum(file, HashAlgo::Sha224, options)
47}
48
49#[inline]
50pub fn sha256sum<P: AsRef<Path>>(file: P, options: &Options) -> Result<String, Error> {
51    checksum(file, HashAlgo::Sha256, options)
52}
53
54#[inline]
55pub fn sha384sum<P: AsRef<Path>>(file: P, options: &Options) -> Result<String, Error> {
56    checksum(file, HashAlgo::Sha384, options)
57}
58
59#[inline]
60pub fn sha512sum<P: AsRef<Path>>(file: P, options: &Options) -> Result<String, Error> {
61    checksum(file, HashAlgo::Sha512, options)
62}
63
64#[derive(Debug)]
65pub struct Options {
66    /// Read in binary mode or text mode. On windows this is true.
67    pub binary_mode: bool,
68}
69
70#[inline]
71fn default_binary_mode() -> bool {
72    cfg!(windows)
73}
74
75impl Default for Options {
76    fn default() -> Self {
77        Options {
78            binary_mode: default_binary_mode(),
79        }
80    }
81}
82
83#[derive(Debug)]
84pub struct CheckOptions {
85    /// Read in binary mode or text mode. On windows this is true.
86    pub binary_mode: bool,
87
88    pub status: bool,
89    pub quiet: bool,
90    pub strict: bool,
91    pub warn: bool,
92}
93
94impl Default for CheckOptions {
95    fn default() -> Self {
96        CheckOptions {
97            binary_mode: default_binary_mode(),
98            status: false,
99            quiet: false,
100            strict: false,
101            warn: false,
102        }
103    }
104}
105
106/// Read MD5 sums from the FILEs and check them.
107pub fn md5sum_check<P: AsRef<Path>>(_file: P, _option: &CheckOptions) -> Result<bool, Error> {
108    Ok(false)
109}
110
111#[derive(Debug)]
112enum HashAlgo {
113    B2,
114    Md5,
115    Sha1,
116    Sha224,
117    Sha256,
118    Sha384,
119    Sha512,
120}
121
122trait FusionDigest {
123    fn input(&mut self, input: &[u8]);
124    fn result(&mut self, out: &mut [u8]);
125    fn output_bits(&self) -> usize;
126    fn output_bytes(&self) -> usize {
127        (self.output_bits() + 7) / 8
128    }
129    fn result_str(&mut self) -> String {
130        let mut buf: Vec<u8> = vec![0; self.output_bytes()];
131        self.result(&mut buf);
132        hex::encode(&buf)
133    }
134}
135
136impl FusionDigest for blake2b_simd::State {
137    fn input(&mut self, input: &[u8]) {
138        self.update(input);
139    }
140
141    fn result(&mut self, output: &mut [u8]) {
142        output.copy_from_slice(self.finalize().as_bytes());
143    }
144
145    fn output_bits(&self) -> usize {
146        512
147    }
148}
149
150impl FusionDigest for md5::Context {
151    fn input(&mut self, input: &[u8]) {
152        self.consume(input);
153    }
154
155    fn result(&mut self, output: &mut [u8]) {
156        output.copy_from_slice(&*self.clone().compute());
157    }
158
159    fn output_bits(&self) -> usize {
160        128
161    }
162}
163
164impl FusionDigest for sha1::Sha1 {
165    fn input(&mut self, input: &[u8]) {
166        self.update(input);
167    }
168
169    fn result(&mut self, output: &mut [u8]) {
170        output.copy_from_slice(&self.finalize_reset());
171    }
172
173    fn output_bits(&self) -> usize {
174        160
175    }
176}
177
178macro_rules! impl_digest_for_sha2 {
179    ($type: ty, $bits: expr) => {
180        impl FusionDigest for $type {
181            fn input(&mut self, input: &[u8]) {
182                self.update(input);
183            }
184
185            fn result(&mut self, output: &mut [u8]) {
186                output.copy_from_slice(&self.clone().finalize());
187            }
188
189            fn output_bits(&self) -> usize {
190                $bits
191            }
192        }
193    };
194}
195
196impl_digest_for_sha2!(sha2::Sha224, 224);
197impl_digest_for_sha2!(sha2::Sha256, 256);
198impl_digest_for_sha2!(sha2::Sha384, 384);
199impl_digest_for_sha2!(sha2::Sha512, 512);
200
201fn digest_for_algo(algo: HashAlgo) -> Box<dyn FusionDigest> {
202    match algo {
203        HashAlgo::B2 => Box::new(blake2b_simd::State::new()),
204        HashAlgo::Md5 => Box::new(md5::Context::new()),
205        HashAlgo::Sha1 => Box::new(sha1::Sha1::new()),
206        HashAlgo::Sha224 => Box::new(sha2::Sha224::new()),
207        HashAlgo::Sha256 => Box::new(sha2::Sha256::new()),
208        HashAlgo::Sha384 => Box::new(sha2::Sha384::new()),
209        HashAlgo::Sha512 => Box::new(sha2::Sha512::new()),
210    }
211}
212
213fn checksum<P: AsRef<Path>>(file: P, algo: HashAlgo, _options: &Options) -> Result<String, Error> {
214    let mut digest = digest_for_algo(algo);
215    let mut reader = File::open(&file)?;
216
217    let mut buffer = Vec::with_capacity(16 * 1024);
218    loop {
219        let n_read = reader.read_to_end(&mut buffer)?;
220        if n_read == 0 {
221            break;
222        }
223        digest.input(&buffer[..n_read]);
224    }
225
226    Ok(digest.result_str())
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    const RUST_WIKIPEDIA: &str = "tests/Rust_Wikipedia.pdf";
234
235    #[test]
236    fn test_crc32() {
237        let ret = crc32(RUST_WIKIPEDIA);
238        assert!(ret.is_ok());
239        let hex_str = ret.unwrap();
240        assert_eq!(&hex_str, "2751965731");
241    }
242
243    #[test]
244    fn test_b2sum() {
245        let ret = b2sum(RUST_WIKIPEDIA, &Options::default());
246        assert!(ret.is_ok());
247        let hex_str = ret.unwrap();
248        assert_eq!(&hex_str, "e5bd6bbb61edd4cc800ed4982a8226cf0f9582b717702ee5ff79fa286b0f6c70b0533d1f63661b50fa5e739dd78e74616955d7008d6f5c18715ee52235ef32a3");
249    }
250
251    #[test]
252    fn test_md5sum() {
253        let ret = md5sum(RUST_WIKIPEDIA, &Options::default());
254        assert!(ret.is_ok());
255        let hex_str = ret.unwrap();
256        assert_eq!(&hex_str, "c7f5281f3a03cdd3a247966869cf0ba3");
257    }
258
259    #[test]
260    fn test_sha1sum() {
261        let ret = sha1sum(RUST_WIKIPEDIA, &Options::default());
262        assert!(ret.is_ok());
263        let hex_str = ret.unwrap();
264        assert_eq!(&hex_str, "671c0b590385ac473ec3bd4d1c196363252d5d2b");
265    }
266
267    #[test]
268    fn test_sha224sum() {
269        let ret = sha224sum(RUST_WIKIPEDIA, &Options::default());
270        assert!(ret.is_ok());
271        let hex_str = ret.unwrap();
272        assert_eq!(
273            &hex_str,
274            "2cbb67e54546408c512cef08964bb49a3071452347ce1e0ced931a0d"
275        );
276    }
277
278    #[test]
279    fn test_sha256sum() {
280        let ret = sha256sum(RUST_WIKIPEDIA, &Options::default());
281        assert!(ret.is_ok());
282        let hex_str = ret.unwrap();
283        assert_eq!(
284            &hex_str,
285            "79e1819c944cdbe4c02b92647d63a9f9d18f6cc6f3795dcedecc685a8a4d476b"
286        );
287    }
288
289    #[test]
290    fn test_sha384sum() {
291        let ret = sha384sum(RUST_WIKIPEDIA, &Options::default());
292        assert!(ret.is_ok());
293        let hex_str = ret.unwrap();
294        assert_eq!(
295            &hex_str,
296            "e45720b17f461732d4787edd37e23f62149ffb05482fbf2201b885ea6d4f79afcbbd1134112e56d83a479b8fdc02d6cf"
297        );
298    }
299
300    #[test]
301    fn test_sha512sum() {
302        let ret = sha512sum(RUST_WIKIPEDIA, &Options::default());
303        assert!(ret.is_ok());
304        let hex_str = ret.unwrap();
305        assert_eq!(
306            &hex_str,
307            "91a731cb4e6d2f3e63eaff98a99ea0dbd7d3ad70c2e671f533dd98271d270f63356bf8dd154a73d539548a0aed1dcfc69f499ed72d91655f451bb029bbd26c82"
308        );
309    }
310}