use crate::codec::DAG_PROTOBUF;
use crate::error::{Error, Result};
use crate::version::Version;
use tiny_multihash::{Multihash, U64};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Decode))]
#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode))]
#[cfg_attr(feature = "serde-codec", derive(serde::Deserialize))]
#[cfg_attr(feature = "serde-codec", derive(serde::Serialize))]
pub struct Cid {
version: Version,
codec: u64,
hash: Multihash<U64>,
}
impl Cid {
pub fn new_v0(hash: Multihash<U64>) -> Result<Self> {
if hash.code() != 0x12 && hash.size() != 0x20 {
return Err(Error::InvalidCidV0Multihash);
}
Ok(Self {
version: Version::V0,
codec: DAG_PROTOBUF,
hash,
})
}
pub fn new_v1(codec: u64, hash: Multihash<U64>) -> Self {
Self {
version: Version::V1,
codec,
hash,
}
}
pub fn new(version: Version, codec: u64, hash: Multihash<U64>) -> Result<Self> {
match version {
Version::V0 => {
if codec != DAG_PROTOBUF {
return Err(Error::InvalidCidV0Codec);
}
Self::new_v0(hash)
}
Version::V1 => Ok(Self::new_v1(codec, hash)),
}
}
pub fn version(&self) -> Version {
self.version
}
pub fn codec(&self) -> u64 {
self.codec
}
pub fn hash(&self) -> &Multihash<U64> {
&self.hash
}
#[cfg(feature = "std")]
pub fn read_bytes<R: std::io::Read>(mut r: R) -> Result<Self> {
use core::convert::TryFrom;
use unsigned_varint::io::read_u64;
let version = read_u64(&mut r)?;
let codec = read_u64(&mut r)?;
if [version, codec] == [0x12, 0x20] {
let mut digest = [0u8; 32];
r.read_exact(&mut digest)?;
let mh = Multihash::wrap(version, &digest).unwrap();
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 write_bytes_v1<W: std::io::Write>(&self, mut w: W) -> Result<()> {
use unsigned_varint::encode as varint_encode;
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(())
}
#[cfg(feature = "std")]
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(())
}
#[cfg(feature = "std")]
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = vec![];
self.write_bytes(&mut bytes).unwrap();
bytes
}
#[cfg(feature = "std")]
fn to_string_v0(&self) -> String {
multibase::Base::Base58Btc.encode(self.hash.to_bytes())
}
#[cfg(feature = "std")]
fn to_string_v1(&self) -> String {
multibase::encode(multibase::Base::Base32Lower, self.to_bytes().as_slice())
}
#[cfg(feature = "std")]
pub fn to_string_of_base(&self, base: multibase::Base) -> Result<String> {
match self.version {
Version::V0 => {
if base == multibase::Base::Base58Btc {
Ok(self.to_string_v0())
} else {
Err(Error::InvalidCidV0Base)
}
}
Version::V1 => Ok(multibase::encode(base, self.to_bytes())),
}
}
}
impl Default for Cid {
fn default() -> Self {
Self {
version: Version::V1,
codec: 0,
hash: Multihash::default(),
}
}
}
#[cfg(feature = "std")]
#[allow(clippy::derive_hash_xor_eq)]
impl core::hash::Hash for Cid {
fn hash<T: core::hash::Hasher>(&self, state: &mut T) {
self.to_bytes().hash(state);
}
}
#[cfg(feature = "std")]
impl core::fmt::Display for Cid {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let output = match self.version {
Version::V0 => self.to_string_v0(),
Version::V1 => self.to_string_v1(),
};
write!(f, "{}", output)
}
}
#[cfg(feature = "std")]
impl core::str::FromStr for Cid {
type Err = Error;
fn from_str(cid_str: &str) -> Result<Self> {
use core::convert::TryFrom;
Self::try_from(cid_str)
}
}
#[cfg(feature = "std")]
impl core::convert::TryFrom<String> for Cid {
type Error = Error;
fn try_from(cid_str: String) -> Result<Self> {
Self::try_from(cid_str.as_str())
}
}
#[cfg(feature = "std")]
impl core::convert::TryFrom<&str> for Cid {
type Error = Error;
fn try_from(cid_str: &str) -> Result<Self> {
static IPFS_DELIMETER: &str = "/ipfs/";
let hash = match cid_str.find(IPFS_DELIMETER) {
Some(index) => &cid_str[index + IPFS_DELIMETER.len()..],
_ => cid_str,
};
if hash.len() < 2 {
return Err(Error::InputTooShort);
}
let decoded = if Version::is_v0_str(hash) {
multibase::Base::Base58Btc.decode(hash)?
} else {
let (_, decoded) = multibase::decode(hash)?;
decoded
};
Self::try_from(decoded)
}
}
#[cfg(feature = "std")]
impl core::convert::TryFrom<Vec<u8>> for Cid {
type Error = Error;
fn try_from(bytes: Vec<u8>) -> Result<Self> {
Self::try_from(bytes.as_slice())
}
}
#[cfg(feature = "std")]
impl core::convert::TryFrom<&[u8]> for Cid {
type Error = Error;
fn try_from(mut bytes: &[u8]) -> Result<Self> {
Self::read_bytes(&mut bytes)
}
}
impl From<&Cid> for Cid {
fn from(cid: &Cid) -> Self {
*cid
}
}
#[cfg(feature = "std")]
impl From<Cid> for Vec<u8> {
fn from(cid: Cid) -> Self {
cid.to_bytes()
}
}
#[cfg(feature = "std")]
impl From<Cid> for String {
fn from(cid: Cid) -> Self {
cid.to_string()
}
}
#[cfg(feature = "std")]
impl<'a> From<Cid> for std::borrow::Cow<'a, Cid> {
fn from(from: Cid) -> Self {
std::borrow::Cow::Owned(from)
}
}
#[cfg(feature = "std")]
impl<'a> From<&'a Cid> for std::borrow::Cow<'a, Cid> {
fn from(from: &'a Cid) -> Self {
std::borrow::Cow::Borrowed(from)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(feature = "scale-codec")]
fn test_cid_scale_codec() {
use super::Cid;
use parity_scale_codec::{Decode, Encode};
let cid = Cid::default();
let bytes = cid.encode();
let cid2 = Cid::decode(&mut &bytes[..]).unwrap();
assert_eq!(cid, cid2);
}
}