Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ edition = "2018"
[dependencies]
blake2b_simd = { version = "0.5.9", default-features = false }
blake2s_simd = { version = "0.5.9", default-features = false }
bytes = "0.5"
sha1 = "0.5"
sha2 = { version = "0.7", default-features = false }
tiny-keccak = "1.4"
Expand Down
68 changes: 47 additions & 21 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,22 @@

mod errors;
mod hashes;
mod storage;

use std::convert::TryFrom;
use std::fmt::Debug;
use std::hash;

use blake2b_simd::{blake2b, Params as Blake2bVariable};
use blake2s_simd::{blake2s, Params as Blake2sVariable};
use bytes::{BufMut, Bytes, BytesMut};
use sha2::Digest;
use tiny_keccak::Keccak;
use unsigned_varint::{decode, encode};

pub use errors::{DecodeError, DecodeOwnedError, EncodeError};
pub use hashes::Hash;
use std::fmt;
use storage::Storage;

// Helper macro for encoding input into output using sha1, sha2, tiny_keccak, or blake2
macro_rules! encode {
Expand Down Expand Up @@ -107,12 +111,12 @@ pub fn encode(hash: Hash, input: &[u8]) -> Result<Multihash, EncodeError> {

let total_len = code.len() + size.len() + input.len();

let mut output = BytesMut::with_capacity(total_len);
output.put_slice(code);
output.put_slice(size);
output.put_slice(input);
let mut output = Vec::with_capacity(total_len);
output.extend_from_slice(code);
output.extend_from_slice(size);
output.extend_from_slice(input);
Ok(Multihash {
bytes: output.freeze(),
storage: Storage::from_slice(&output),
Comment thread
rklaehn marked this conversation as resolved.
Outdated
})
} else {
let (offset, mut output) = encode_hash(hash);
Expand All @@ -135,31 +139,51 @@ pub fn encode(hash: Hash, input: &[u8]) -> Result<Multihash, EncodeError> {
});

Ok(Multihash {
bytes: output.freeze(),
storage: Storage::from_slice(&output),
Comment thread
rklaehn marked this conversation as resolved.
})
}
}

// Encode the given [`Hash`] value and ensure the returned [`BytesMut`]
// Encode the given [`Hash`] value and ensure the returned [`Vec<u8>`]
// has enough capacity to hold the actual digest.
fn encode_hash(hash: Hash) -> (usize, BytesMut) {
fn encode_hash(hash: Hash) -> (usize, Vec<u8>) {
let mut buf = encode::u16_buffer();
let code = encode::u16(hash.code(), &mut buf);

let len = code.len() + 1 + usize::from(hash.size());

let mut output = BytesMut::with_capacity(len);
output.put_slice(code);
output.put_u8(hash.size());
let mut output = Vec::with_capacity(len);
output.extend_from_slice(code);
output.push(hash.size());
output.resize(len, 0);

(code.len() + 1, output)
}

/// Represents a valid multihash.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Clone)]
pub struct Multihash {
bytes: Bytes,
storage: Storage,
}

impl Debug for Multihash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Multihash")
}
}

impl PartialEq for Multihash {
fn eq(&self, other: &Self) -> bool {
self.storage.bytes() == other.storage.bytes()
}
}

impl Eq for Multihash {}

impl hash::Hash for Multihash {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.storage.bytes().hash(state);
}
}

impl Multihash {
Expand All @@ -172,7 +196,7 @@ impl Multihash {
});
}
Ok(Multihash {
bytes: Bytes::from(bytes),
storage: Storage::from_slice(&bytes),
Comment thread
rklaehn marked this conversation as resolved.
})
}

Expand All @@ -183,17 +207,19 @@ impl Multihash {

/// Returns the bytes representation of the multihash.
pub fn to_vec(&self) -> Vec<u8> {
Vec::from(&self.bytes[..])
Vec::from(self.as_bytes())
}

/// Returns the bytes representation of this multihash.
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
self.storage.bytes()
}

/// Builds a `MultihashRef` corresponding to this `Multihash`.
pub fn as_ref(&self) -> MultihashRef {
MultihashRef { bytes: &self.bytes }
MultihashRef {
bytes: self.as_bytes(),
}
}

/// Returns which hashing algorithm is used in this multihash.
Expand All @@ -215,7 +241,7 @@ impl AsRef<[u8]> for Multihash {

impl<'a> PartialEq<MultihashRef<'a>> for Multihash {
fn eq(&self, other: &MultihashRef<'a>) -> bool {
&*self.bytes == other.bytes
&*self.as_bytes() == other.as_bytes()
}
}

Expand Down Expand Up @@ -290,7 +316,7 @@ impl<'a> MultihashRef<'a> {
/// This operation allocates.
pub fn to_owned(&self) -> Multihash {
Multihash {
bytes: Bytes::copy_from_slice(self.bytes),
storage: Storage::from_slice(self.bytes),
}
}

Expand All @@ -302,7 +328,7 @@ impl<'a> MultihashRef<'a> {

impl<'a> PartialEq<Multihash> for MultihashRef<'a> {
fn eq(&self, other: &Multihash) -> bool {
self.bytes == &*other.bytes
self.as_bytes() == &*other.as_bytes()
}
}

Expand Down
55 changes: 55 additions & 0 deletions src/storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use std::sync::Arc;

const MAX_INLINE: usize = 38;
Comment thread
rklaehn marked this conversation as resolved.

#[derive(Clone)]
pub enum Storage {
Comment thread
rklaehn marked this conversation as resolved.
Outdated
/// hash is stored inline if it is smaller than MAX_INLINE
Inline(u8, [u8; MAX_INLINE]),
/// hash is stored on the heap. this must be only used if the hash is actually larger than
/// MAX_INLINE bytes to ensure an unique representation.
Heap(Arc<[u8]>),
}

impl Storage {
/// The raw bytes.
pub fn bytes(&self) -> &[u8] {
match self {
Storage::Inline(len, bytes) => &bytes[..(*len as usize)],
Storage::Heap(data) => &data,
}
}

/// creates storage from a vec. For a size up to MAX_INLINE, this will not allocate.
pub fn from_slice(slice: &[u8]) -> Self {
let len = slice.len();
if len <= MAX_INLINE {
let mut data: [u8; MAX_INLINE] = [0; MAX_INLINE];
data[..len].copy_from_slice(slice);
Storage::Inline(len as u8, data)
} else {
Storage::Heap(slice.into())
}
}
}

#[cfg(test)]
mod tests {
use super::{Storage, MAX_INLINE};

#[test]
fn struct_size() {
// this should be true for both 32 and 64 bit archs
assert_eq!(std::mem::size_of::<Storage>(), 40);
}

#[test]
fn roundtrip() {
// check that .bytes() returns whatever the storage was created with
for i in 0..((MAX_INLINE + 10) as u8) {
let data = (0..i).collect::<Vec<u8>>();
let storage = Storage::from_slice(&data);
assert_eq!(data, storage.bytes());
}
}
}