Skip to content

Commit

Permalink
Add support for hashing from a reader
Browse files Browse the repository at this point in the history
fixes #141
  • Loading branch information
Stebalien committed Nov 9, 2021
1 parent 184ed59 commit f002173
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 12 deletions.
2 changes: 1 addition & 1 deletion derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ std = []

[dev-dependencies]
pretty_assertions = "1.0.0"
multihash = { path = "..", default-features = false, features = ["derive", "sha2"] }
multihash = { path = "..", default-features = false, features = ["derive", "sha2", "std"] }
2 changes: 1 addition & 1 deletion derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
//! use multihash::MultihashDigest;
//!
//! #[derive(Clone, Copy, Debug, Eq, Multihash, PartialEq)]
//! #[mh(alloc_size = 64)]
//! #[mh(alloc_size = 64, io_path = ::std::io)]
//! pub enum Code {
//! #[mh(code = 0x01, hasher = multihash::Sha2_256)]
//! Foo,
Expand Down
58 changes: 53 additions & 5 deletions derive/src/multihash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod kw {
custom_keyword!(hasher);
custom_keyword!(mh);
custom_keyword!(alloc_size);
custom_keyword!(io_path);
}

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

impl Parse for DeriveAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(kw::alloc_size) {
Ok(Self::AllocSize(input.parse()?))
} else if input.peek(kw::io_path) {
Ok(Self::IoPath(input.parse()?))
} else {
Err(syn::Error::new(input.span(), "unknown attribute"))
}
Expand Down Expand Up @@ -89,6 +93,17 @@ impl Hash {
Multihash::wrap(#code, hasher.finalize()).unwrap()
})
}

fn code_reader(&self) -> TokenStream {
let ident = &self.ident;
let hasher = &self.hasher;
let code = &self.code;
quote!(Self::#ident => {
let mut hasher = #hasher::default();
io::copy(reader, &mut hasher)?;
Ok(Multihash::wrap(#code, hasher.finalize()).unwrap())
})
}
}

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

let mut io_path = syn::parse_quote!(::std::io);

for attr in &ast.attrs {
let derive_attrs: Result<utils::Attrs<DeriveAttr>, _> = syn::parse2(attr.tokens.clone());
if let Ok(derive_attrs) = derive_attrs {
Expand All @@ -145,12 +162,15 @@ fn parse_code_enum_attrs(ast: &syn::DeriveInput) -> syn::LitInt {
DeriveAttr::AllocSize(alloc_size_attr) => {
alloc_size = Some(alloc_size_attr.value)
}
DeriveAttr::IoPath(io_path_attr) => {
io_path = io_path_attr.value;
}
}
}
}
}
match alloc_size {
Some(alloc_size) => alloc_size,
Some(alloc_size) => (alloc_size, io_path),
None => {
let msg = "enum is missing `alloc_size` attribute: e.g. #[mh(alloc_size = 64)]";
#[cfg(test)]
Expand Down Expand Up @@ -206,7 +226,7 @@ pub fn multihash(s: Structure) -> TokenStream {
}
};
let code_enum = &s.ast().ident;
let alloc_size = parse_code_enum_attrs(s.ast());
let (alloc_size, io_path) = parse_code_enum_attrs(s.ast());
let hashes: Vec<_> = s.variants().iter().map(Hash::from).collect();

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

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

fn digest_reader<R: #io_path::Read>(&self, reader: &mut R) -> #io_path::Result<Multihash> {
use #io_path;
use #mh_crate::Hasher;
match self {
#(#code_reader,)*
_ => unreachable!(),
}
}

fn wrap(&self, digest: &[u8]) -> Result<Multihash, #mh_crate::Error> {
Multihash::wrap((*self).into(), digest)
}
Expand Down Expand Up @@ -298,9 +328,27 @@ mod tests {
}
}

fn wrap(&self, digest: &[u8]) -> Result<Multihash, multihash::Error> {
Multihash::wrap((*self).into(), digest)
fn digest_reader<R: ::std::io::Read>(&self, reader: &mut R) -> ::std::io::Result<Multihash> {
use ::std::io;
use multihash::Hasher;
match self {
Self::Identity256 => {
let mut hasher = multihash::Identity256::default();
io::copy(reader, &mut hasher)?;
Ok(Multihash::wrap(multihash::IDENTITY, hasher.finalize()).unwrap())
},
Self::Strobe256 => {
let mut hasher = multihash::Strobe256::default();
io::copy(reader, &mut hasher)?;
Ok(Multihash::wrap(0x38b64f, hasher.finalize()).unwrap())
},
_ => unreachable!(),
}
}

fn wrap(&self, digest: &[u8]) -> Result<Multihash, multihash::Error> {
Multihash::wrap((*self).into(), digest)
}
}

impl From<Code> for u64 {
Expand Down
2 changes: 1 addition & 1 deletion derive/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::Error;

pub fn use_crate(name: &str) -> Result<syn::Ident, Error> {
pub(crate) fn use_crate(name: &str) -> Result<syn::Ident, Error> {
match crate_name(name) {
Ok(FoundCrate::Name(krate)) => Ok(syn::Ident::new(&krate, Span::call_site())),
Ok(FoundCrate::Itself) => Ok(syn::Ident::new("crate", Span::call_site())),
Expand Down
15 changes: 15 additions & 0 deletions examples/custom_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ use std::convert::TryFrom;
use multihash::derive::Multihash;
use multihash::{Error, Hasher, MultihashDigest, MultihashGeneric, Sha2_256};

#[cfg(feature = "std")]
use std::io;

#[cfg(not(feature = "std"))]
use core2::io;

// You can implement a custom hasher. This is a SHA2 256-bit hasher that returns a hash that is
// truncated to 160 bits.
#[derive(Default, Debug)]
Expand All @@ -19,6 +25,15 @@ impl Hasher for Sha2_256Truncated20 {
}
}

impl io::Write for Sha2_256Truncated20 {
fn write(&mut self, input: &[u8]) -> io::Result<usize> {
self.0.write(input)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}

#[derive(Clone, Copy, Debug, Eq, Multihash, PartialEq)]
#[mh(alloc_size = 64)]
pub enum Code {
Expand Down
8 changes: 7 additions & 1 deletion src/hasher.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
#[cfg(feature = "std")]
use std::io;

#[cfg(not(feature = "std"))]
use core2::io;

/// Trait implemented by a hash function implementation.
pub trait Hasher {
pub trait Hasher: io::Write {
/// Consume input and update internal state.
fn update(&mut self, input: &[u8]);

Expand Down
26 changes: 24 additions & 2 deletions src/multihash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,27 @@ pub trait MultihashDigest<const S: usize>:
/// let hash = Code::Sha3_256.digest(b"Hello world!");
/// println!("{:02x?}", hash);
/// ```
fn digest(&self, input: &[u8]) -> Multihash<S>;
fn digest(&self, input: &[u8]) -> Multihash<S> {
let mut input = input;
self.digest_reader(&mut input).unwrap()
}

/// Calculate the hash of some input stream.
///
/// # Example
///
/// ```
/// // `Code` implements `MultihashDigest`
/// use multihash::{Code, MultihashDigest};
///
/// let mut data = std::io::Cursor::new(b"Hello world!");
///
/// let hash = Code::Sha3_256.digest_reader(&mut data).unwrap();
/// println!("{:02x?}", hash);
/// ```
fn digest_reader<R: io::Read>(&self, input: &mut R) -> io::Result<Multihash<S>>
where
Self: Sized;

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

/// A Multihash instance that only supports the basic functionality and no hashing.
Expand Down
3 changes: 2 additions & 1 deletion src/multihash_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ pub use multihash_derive::Multihash;
///
/// [`Multihash` derive]: crate::derive
#[derive(Copy, Clone, Debug, Eq, Multihash, PartialEq)]
#[mh(alloc_size = 64)]
#[cfg_attr(feature = "std", mh(alloc_size = 64, io_path = ::std::io))]
#[cfg_attr(not(feature = "std"), mh(alloc_size = 64, io_path = ::core2::io))]
pub enum Code {
/// SHA-256 (32-byte hash size)
#[cfg(feature = "sha2")]
Expand Down
7 changes: 7 additions & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ macro_rules! assert_encode {
expected,
"{:?} encodes correctly (from hasher)", stringify!($alg)
);

// From a reader.
assert_eq!(
$code.digest_reader(&mut Cursor::new($data)).unwrap().to_bytes(),
expected,
"{:?} encodes correctly (from hasher)", stringify!($alg)
);
)*
}
}
Expand Down

0 comments on commit f002173

Please sign in to comment.