dropbox_content_hasher/
lib.rs1#![deny(unused_must_use, missing_debug_implementations)]
2#![warn(rust_2018_idioms)]
3
4use digest;
5
6use digest::generic_array::typenum::U64;
7use digest::generic_array::GenericArray;
8use digest::{Reset, Digest, FixedOutput, Input};
9use sha2::Sha256;
10
11use std::fs::File;
12use std::io::Read;
13use std::path::Path;
14
15pub const BLOCK_SIZE: usize = 4 * 1024 * 1024;
16
17#[derive(Clone, Debug)]
28pub struct DropboxContentHasher {
29 overall_hasher: Sha256,
30 block_hasher: Sha256,
31 block_pos: usize,
32}
33
34impl DropboxContentHasher {
35 pub fn new() -> Self {
36 DropboxContentHasher {
37 overall_hasher: Sha256::new(),
38 block_hasher: Sha256::new(),
39 block_pos: 0,
40 }
41 }
42
43 pub fn hash_file<T>(path: T) -> std::io::Result<GenericArray<u8, <Self as FixedOutput>::OutputSize>>
57 where T: AsRef<Path> {
58 let file = File::open(&path)?;
59 return DropboxContentHasher::hash_reader(&file);
60 }
61
62 pub fn hash_reader<T>(mut reader: T) -> std::io::Result<GenericArray<u8, <Self as FixedOutput>::OutputSize>>
75 where T: Read {
76 let mut hasher = DropboxContentHasher::new();
77 let mut buf = vec![0; BLOCK_SIZE];
78 loop {
79 let len = reader.read(&mut buf)?;
80 if len == 0 { break; }
81 Input::input(&mut hasher, &buf[..len])
82 }
83 Ok(hasher.result())
84 }
85}
86
87impl Default for DropboxContentHasher {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93impl Reset for DropboxContentHasher {
94 fn reset(&mut self) {
95 self.overall_hasher = Sha256::new();
96 self.block_hasher = Sha256::new();
97 self.block_pos = 0;
98 }
99}
100
101impl Input for DropboxContentHasher {
102 fn input<B: AsRef<[u8]>>(&mut self, data: B) {
103 let mut input = data.as_ref();
104 while input.len() > 0 {
105 if self.block_pos == BLOCK_SIZE {
106 let block_hasher = self.block_hasher.clone();
107 Input::input(&mut self.overall_hasher, block_hasher.result().as_slice());
108 self.block_hasher = Sha256::new();
109 self.block_pos = 0;
110 }
111
112 let space_in_block = BLOCK_SIZE - self.block_pos;
113 let (head, rest) = input.split_at(::std::cmp::min(input.len(), space_in_block));
114 Input::input(&mut self.block_hasher, head);
115
116 self.block_pos += head.len();
117 input = rest;
118 }
119 }
120}
121
122impl FixedOutput for DropboxContentHasher {
123 type OutputSize = <Sha256 as FixedOutput>::OutputSize;
124
125 fn fixed_result(mut self) -> GenericArray<u8, Self::OutputSize> {
126 if self.block_pos > 0 {
127 Input::input(&mut self.overall_hasher, self.block_hasher.result().as_slice());
128 }
129 self.overall_hasher.result()
130 }
131}
132
133impl digest::BlockInput for DropboxContentHasher {
134 type BlockSize = U64;
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use std::fs::File;
141
142 #[test]
143 fn test_vector() {
144 let expected = "485291fa0ee50c016982abbfa943957bcd231aae0492ccbaa22c58e3997b35e0".to_string();
145 let mut file = File::open("test-data/milky-way-nasa.jpg").expect("Couldn't open test file");
146
147 let result = DropboxContentHasher::hash_reader(&mut file).expect("Couldn't hash test file");
148
149 let hex_hash = format!("{:x}", result);
150 assert_eq!(hex_hash, expected);
151 }
152}