Skip to content

Commit

Permalink
feat: introduce read_bytes() and write_bytes()
Browse files Browse the repository at this point in the history
This commit is based on @dvc94ch's work at https://github.com/ipfs-rust/tiny-cid

BREAKING CHANGE: The `Error` type no longer implements PartialEq, Eq, Clone, Copy.

The reason is that there is now also an IO error possible, which doesn't implement
those either.
  • Loading branch information
vmx committed Nov 10, 2020
1 parent fbd5550 commit eeee0de
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 46 deletions.
87 changes: 48 additions & 39 deletions src/cid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::convert::TryFrom;
use multibase::{encode as base_encode, Base};
use multihash::{Multihash, Size};
#[cfg(feature = "std")]
use unsigned_varint::{decode as varint_decode, encode as varint_encode};
use unsigned_varint::{encode as varint_encode, io::read_u64 as varint_read_u64};

use crate::error::{Error, Result};
use crate::version::Version;
Expand Down Expand Up @@ -87,43 +87,64 @@ impl<S: Size> Cid<S> {
&self.hash
}

/// Reads the bytes from a byte stream.
#[cfg(feature = "std")]
fn to_string_v0(&self) -> String {
Base::Base58Btc.encode(self.hash.to_bytes())
pub fn read_bytes<R: std::io::Read>(mut r: R) -> Result<Self> {
let version = varint_read_u64(&mut r)?;
let codec = varint_read_u64(&mut r)?;
// CIDv0 has the fixed `0x12 0x20` prefix
if [version, codec] == [0x12, 0x20] {
let mut digest = [0u8; 32];
r.read_exact(&mut digest)?;
let mh = Multihash::wrap(version, &digest).expect("Digest is always 32 bytes.");
Self::new_v0(mh)
} else {
let version = Version::try_from(version)?;
let mh = Multihash::read(r)?;
Self::new(version, codec, mh)
}
}

#[cfg(feature = "std")]
fn to_string_v1(&self) -> String {
multibase::encode(Base::Base32Lower, self.to_bytes().as_slice())
fn write_bytes_v1<W: std::io::Write>(&self, mut w: W) -> Result<()> {
let mut version_buf = varint_encode::u64_buffer();
let version = varint_encode::u64(self.version.into(), &mut version_buf);

let mut codec_buf = varint_encode::u64_buffer();
let codec = varint_encode::u64(self.codec, &mut codec_buf);

w.write_all(version)?;
w.write_all(codec)?;
self.hash.write(&mut w)?;
Ok(())
}

/// Writes the bytes to a byte stream.
#[cfg(feature = "std")]
fn to_bytes_v0(&self) -> Vec<u8> {
self.hash.to_bytes()
pub fn write_bytes<W: std::io::Write>(&self, w: W) -> Result<()> {
match self.version {
Version::V0 => self.hash.write(w)?,
Version::V1 => self.write_bytes_v1(w)?,
}
Ok(())
}

/// Returns the encoded bytes of the `Cid`.
#[cfg(feature = "std")]
fn to_bytes_v1(&self) -> Vec<u8> {
let mut res = Vec::with_capacity(16);

let mut buf = varint_encode::u64_buffer();
let version = varint_encode::u64(self.version.into(), &mut buf);
res.extend_from_slice(version);
let mut buf = varint_encode::u64_buffer();
let codec = varint_encode::u64(self.codec, &mut buf);
res.extend_from_slice(codec);
res.extend_from_slice(&self.hash.to_bytes());

res
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = vec![];
self.write_bytes(&mut bytes).unwrap();
bytes
}

/// Convert CID to encoded bytes.
#[cfg(feature = "std")]
pub fn to_bytes(&self) -> Vec<u8> {
match self.version {
Version::V0 => self.to_bytes_v0(),
Version::V1 => self.to_bytes_v1(),
}
fn to_string_v0(&self) -> String {
Base::Base58Btc.encode(self.hash.to_bytes())
}

#[cfg(feature = "std")]
fn to_string_v1(&self) -> String {
multibase::encode(Base::Base32Lower, self.to_bytes().as_slice())
}

/// Convert CID into a multibase encoded string
Expand Down Expand Up @@ -233,20 +254,8 @@ impl<S: Size> TryFrom<Vec<u8>> for Cid<S> {
impl<S: Size> TryFrom<&[u8]> for Cid<S> {
type Error = Error;

fn try_from(bytes: &[u8]) -> Result<Self> {
if Version::is_v0_binary(bytes) {
let mh = Multihash::from_bytes(bytes)?;
Self::new_v0(mh)
} else {
let (raw_version, remain) = varint_decode::u64(&bytes)?;
let version = Version::try_from(raw_version)?;

let (codec, hash) = varint_decode::u64(&remain)?;

let mh = Multihash::from_bytes(hash)?;

Self::new(version, codec, mh)
}
fn try_from(mut bytes: &[u8]) -> Result<Self> {
Self::read_bytes(&mut bytes)
}
}

Expand Down
27 changes: 25 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use core::fmt;
pub type Result<T> = core::result::Result<T, Error>;

/// Error types
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(Debug)]
pub enum Error {
/// Unknown CID codec.
UnknownCodec,
Expand All @@ -22,6 +22,9 @@ pub enum Error {
InvalidCidV0Base,
/// Varint decode failure.
VarIntDecodeError,
/// Io error.
#[cfg(feature = "std")]
Io(std::io::Error),
}

#[cfg(feature = "std")]
Expand All @@ -30,7 +33,7 @@ impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
let error = match *self {
let error = match self {
UnknownCodec => "Unknown codec",
InputTooShort => "Input too short",
ParsingError => "Failed to parse multihash",
Expand All @@ -39,6 +42,8 @@ impl fmt::Display for Error {
InvalidCidV0Multihash => "CIDv0 requires a Sha-256 multihash",
InvalidCidV0Base => "CIDv0 requires a Base58 base",
VarIntDecodeError => "Failed to decode unsigned varint format",
#[cfg(feature = "std")]
Io(err) => return write!(f, "{}", err),
};

f.write_str(error)
Expand All @@ -63,3 +68,21 @@ impl From<unsigned_varint::decode::Error> for Error {
Error::VarIntDecodeError
}
}

#[cfg(feature = "std")]
impl From<unsigned_varint::io::ReadError> for Error {
fn from(err: unsigned_varint::io::ReadError) -> Self {
use unsigned_varint::io::ReadError::*;
match err {
Io(err) => Self::Io(err),
_ => Self::VarIntDecodeError,
}
}
}

#[cfg(feature = "std")]
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Self::Io(err)
}
}
10 changes: 5 additions & 5 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fn basic_marshalling() {

#[test]
fn empty_string() {
assert_eq!(Cid::try_from(""), Err(Error::InputTooShort));
assert!(matches!(Cid::try_from(""), Err(Error::InputTooShort)))
}

#[test]
Expand All @@ -53,13 +53,13 @@ fn from_str() {
assert_eq!(cid.version(), Version::V0);

let bad = "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII".parse::<Cid>();
assert_eq!(bad, Err(Error::ParsingError));
assert!(matches!(bad, Err(Error::ParsingError)));
}

#[test]
fn v0_error() {
let bad = "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII";
assert_eq!(Cid::try_from(bad), Err(Error::ParsingError));
assert!(matches!(Cid::try_from(bad), Err(Error::ParsingError)));
}

#[test]
Expand Down Expand Up @@ -134,10 +134,10 @@ fn to_string_of_base58_v0() {
#[test]
fn to_string_of_base_v0_error() {
let cid = Cid::new_v0(Code::Sha2_256.digest(b"foo")).unwrap();
assert_eq!(
assert!(matches!(
cid.to_string_of_base(Base::Base16Upper),
Err(Error::InvalidCidV0Base)
);
));
}

fn a_function_that_takes_a_generic_cid<S: Size>(cid: &cid::cid::Cid<S>) -> String {
Expand Down

0 comments on commit eeee0de

Please sign in to comment.