Skip to content

Commit f002173

Browse files
committed
Add support for hashing from a reader
fixes #141
1 parent 184ed59 commit f002173

File tree

9 files changed

+111
-12
lines changed

9 files changed

+111
-12
lines changed

derive/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ std = []
2525

2626
[dev-dependencies]
2727
pretty_assertions = "1.0.0"
28-
multihash = { path = "..", default-features = false, features = ["derive", "sha2"] }
28+
multihash = { path = "..", default-features = false, features = ["derive", "sha2", "std"] }

derive/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
//! use multihash::MultihashDigest;
2323
//!
2424
//! #[derive(Clone, Copy, Debug, Eq, Multihash, PartialEq)]
25-
//! #[mh(alloc_size = 64)]
25+
//! #[mh(alloc_size = 64, io_path = ::std::io)]
2626
//! pub enum Code {
2727
//! #[mh(code = 0x01, hasher = multihash::Sha2_256)]
2828
//! Foo,

derive/src/multihash.rs

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod kw {
1717
custom_keyword!(hasher);
1818
custom_keyword!(mh);
1919
custom_keyword!(alloc_size);
20+
custom_keyword!(io_path);
2021
}
2122

2223
/// Attributes for the enum items.
@@ -42,12 +43,15 @@ impl Parse for MhAttr {
4243
#[derive(Debug)]
4344
enum DeriveAttr {
4445
AllocSize(utils::Attr<kw::alloc_size, syn::LitInt>),
46+
IoPath(utils::Attr<kw::io_path, syn::Path>),
4547
}
4648

4749
impl Parse for DeriveAttr {
4850
fn parse(input: ParseStream) -> syn::Result<Self> {
4951
if input.peek(kw::alloc_size) {
5052
Ok(Self::AllocSize(input.parse()?))
53+
} else if input.peek(kw::io_path) {
54+
Ok(Self::IoPath(input.parse()?))
5155
} else {
5256
Err(syn::Error::new(input.span(), "unknown attribute"))
5357
}
@@ -89,6 +93,17 @@ impl Hash {
8993
Multihash::wrap(#code, hasher.finalize()).unwrap()
9094
})
9195
}
96+
97+
fn code_reader(&self) -> TokenStream {
98+
let ident = &self.ident;
99+
let hasher = &self.hasher;
100+
let code = &self.code;
101+
quote!(Self::#ident => {
102+
let mut hasher = #hasher::default();
103+
io::copy(reader, &mut hasher)?;
104+
Ok(Multihash::wrap(#code, hasher.finalize()).unwrap())
105+
})
106+
}
92107
}
93108

94109
impl<'a> From<&'a VariantInfo<'a>> for Hash {
@@ -134,9 +149,11 @@ impl<'a> From<&'a VariantInfo<'a>> for Hash {
134149
///
135150
/// Returns the `alloc_size` and whether errors regarding to `alloc_size` should be reported or not.
136151
#[allow(dead_code)] // TODO
137-
fn parse_code_enum_attrs(ast: &syn::DeriveInput) -> syn::LitInt {
152+
fn parse_code_enum_attrs(ast: &syn::DeriveInput) -> (syn::LitInt, syn::Path) {
138153
let mut alloc_size = None;
139154

155+
let mut io_path = syn::parse_quote!(::std::io);
156+
140157
for attr in &ast.attrs {
141158
let derive_attrs: Result<utils::Attrs<DeriveAttr>, _> = syn::parse2(attr.tokens.clone());
142159
if let Ok(derive_attrs) = derive_attrs {
@@ -145,12 +162,15 @@ fn parse_code_enum_attrs(ast: &syn::DeriveInput) -> syn::LitInt {
145162
DeriveAttr::AllocSize(alloc_size_attr) => {
146163
alloc_size = Some(alloc_size_attr.value)
147164
}
165+
DeriveAttr::IoPath(io_path_attr) => {
166+
io_path = io_path_attr.value;
167+
}
148168
}
149169
}
150170
}
151171
}
152172
match alloc_size {
153-
Some(alloc_size) => alloc_size,
173+
Some(alloc_size) => (alloc_size, io_path),
154174
None => {
155175
let msg = "enum is missing `alloc_size` attribute: e.g. #[mh(alloc_size = 64)]";
156176
#[cfg(test)]
@@ -206,7 +226,7 @@ pub fn multihash(s: Structure) -> TokenStream {
206226
}
207227
};
208228
let code_enum = &s.ast().ident;
209-
let alloc_size = parse_code_enum_attrs(s.ast());
229+
let (alloc_size, io_path) = parse_code_enum_attrs(s.ast());
210230
let hashes: Vec<_> = s.variants().iter().map(Hash::from).collect();
211231

212232
error_code_duplicates(&hashes);
@@ -218,6 +238,7 @@ pub fn multihash(s: Structure) -> TokenStream {
218238
let code_into_u64 = hashes.iter().map(|h| h.code_into_u64(&params));
219239
let code_from_u64 = hashes.iter().map(|h| h.code_from_u64());
220240
let code_digest = hashes.iter().map(|h| h.code_digest());
241+
let code_reader = hashes.iter().map(|h| h.code_reader());
221242

222243
quote! {
223244
/// A Multihash with the same allocated size as the Multihashes produces by this derive.
@@ -232,6 +253,15 @@ pub fn multihash(s: Structure) -> TokenStream {
232253
}
233254
}
234255

256+
fn digest_reader<R: #io_path::Read>(&self, reader: &mut R) -> #io_path::Result<Multihash> {
257+
use #io_path;
258+
use #mh_crate::Hasher;
259+
match self {
260+
#(#code_reader,)*
261+
_ => unreachable!(),
262+
}
263+
}
264+
235265
fn wrap(&self, digest: &[u8]) -> Result<Multihash, #mh_crate::Error> {
236266
Multihash::wrap((*self).into(), digest)
237267
}
@@ -298,9 +328,27 @@ mod tests {
298328
}
299329
}
300330

301-
fn wrap(&self, digest: &[u8]) -> Result<Multihash, multihash::Error> {
302-
Multihash::wrap((*self).into(), digest)
331+
fn digest_reader<R: ::std::io::Read>(&self, reader: &mut R) -> ::std::io::Result<Multihash> {
332+
use ::std::io;
333+
use multihash::Hasher;
334+
match self {
335+
Self::Identity256 => {
336+
let mut hasher = multihash::Identity256::default();
337+
io::copy(reader, &mut hasher)?;
338+
Ok(Multihash::wrap(multihash::IDENTITY, hasher.finalize()).unwrap())
339+
},
340+
Self::Strobe256 => {
341+
let mut hasher = multihash::Strobe256::default();
342+
io::copy(reader, &mut hasher)?;
343+
Ok(Multihash::wrap(0x38b64f, hasher.finalize()).unwrap())
344+
},
345+
_ => unreachable!(),
346+
}
303347
}
348+
349+
fn wrap(&self, digest: &[u8]) -> Result<Multihash, multihash::Error> {
350+
Multihash::wrap((*self).into(), digest)
351+
}
304352
}
305353

306354
impl From<Code> for u64 {

derive/src/utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use syn::parse::{Parse, ParseStream};
44
use syn::punctuated::Punctuated;
55
use syn::Error;
66

7-
pub fn use_crate(name: &str) -> Result<syn::Ident, Error> {
7+
pub(crate) fn use_crate(name: &str) -> Result<syn::Ident, Error> {
88
match crate_name(name) {
99
Ok(FoundCrate::Name(krate)) => Ok(syn::Ident::new(&krate, Span::call_site())),
1010
Ok(FoundCrate::Itself) => Ok(syn::Ident::new("crate", Span::call_site())),

examples/custom_table.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ use std::convert::TryFrom;
33
use multihash::derive::Multihash;
44
use multihash::{Error, Hasher, MultihashDigest, MultihashGeneric, Sha2_256};
55

6+
#[cfg(feature = "std")]
7+
use std::io;
8+
9+
#[cfg(not(feature = "std"))]
10+
use core2::io;
11+
612
// You can implement a custom hasher. This is a SHA2 256-bit hasher that returns a hash that is
713
// truncated to 160 bits.
814
#[derive(Default, Debug)]
@@ -19,6 +25,15 @@ impl Hasher for Sha2_256Truncated20 {
1925
}
2026
}
2127

28+
impl io::Write for Sha2_256Truncated20 {
29+
fn write(&mut self, input: &[u8]) -> io::Result<usize> {
30+
self.0.write(input)
31+
}
32+
fn flush(&mut self) -> io::Result<()> {
33+
Ok(())
34+
}
35+
}
36+
2237
#[derive(Clone, Copy, Debug, Eq, Multihash, PartialEq)]
2338
#[mh(alloc_size = 64)]
2439
pub enum Code {

src/hasher.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
#[cfg(feature = "std")]
2+
use std::io;
3+
4+
#[cfg(not(feature = "std"))]
5+
use core2::io;
6+
17
/// Trait implemented by a hash function implementation.
2-
pub trait Hasher {
8+
pub trait Hasher: io::Write {
39
/// Consume input and update internal state.
410
fn update(&mut self, input: &[u8]);
511

src/multihash.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,27 @@ pub trait MultihashDigest<const S: usize>:
3535
/// let hash = Code::Sha3_256.digest(b"Hello world!");
3636
/// println!("{:02x?}", hash);
3737
/// ```
38-
fn digest(&self, input: &[u8]) -> Multihash<S>;
38+
fn digest(&self, input: &[u8]) -> Multihash<S> {
39+
let mut input = input;
40+
self.digest_reader(&mut input).unwrap()
41+
}
42+
43+
/// Calculate the hash of some input stream.
44+
///
45+
/// # Example
46+
///
47+
/// ```
48+
/// // `Code` implements `MultihashDigest`
49+
/// use multihash::{Code, MultihashDigest};
50+
///
51+
/// let mut data = std::io::Cursor::new(b"Hello world!");
52+
///
53+
/// let hash = Code::Sha3_256.digest_reader(&mut data).unwrap();
54+
/// println!("{:02x?}", hash);
55+
/// ```
56+
fn digest_reader<R: io::Read>(&self, input: &mut R) -> io::Result<Multihash<S>>
57+
where
58+
Self: Sized;
3959

4060
/// Create a multihash from an existing multihash digest.
4161
///
@@ -49,7 +69,9 @@ pub trait MultihashDigest<const S: usize>:
4969
/// let hash = Code::Sha3_256.wrap(&hasher.finalize()).unwrap();
5070
/// println!("{:02x?}", hash);
5171
/// ```
52-
fn wrap(&self, digest: &[u8]) -> Result<Multihash<S>, Error>;
72+
fn wrap(&self, digest: &[u8]) -> Result<Multihash<S>, Error> {
73+
Multihash::wrap((*self).into(), digest)
74+
}
5375
}
5476

5577
/// A Multihash instance that only supports the basic functionality and no hashing.

src/multihash_impl.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ pub use multihash_derive::Multihash;
77
///
88
/// [`Multihash` derive]: crate::derive
99
#[derive(Copy, Clone, Debug, Eq, Multihash, PartialEq)]
10-
#[mh(alloc_size = 64)]
10+
#[cfg_attr(feature = "std", mh(alloc_size = 64, io_path = ::std::io))]
11+
#[cfg_attr(not(feature = "std"), mh(alloc_size = 64, io_path = ::core2::io))]
1112
pub enum Code {
1213
/// SHA-256 (32-byte hash size)
1314
#[cfg(feature = "sha2")]

tests/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ macro_rules! assert_encode {
7070
expected,
7171
"{:?} encodes correctly (from hasher)", stringify!($alg)
7272
);
73+
74+
// From a reader.
75+
assert_eq!(
76+
$code.digest_reader(&mut Cursor::new($data)).unwrap().to_bytes(),
77+
expected,
78+
"{:?} encodes correctly (from hasher)", stringify!($alg)
79+
);
7380
)*
7481
}
7582
}

0 commit comments

Comments
 (0)