use bs58;
use multibase::Base;
use std::{fmt, result, str::FromStr};
use crate::{
multibase::Multibase,
multicodec::{self, Multicodec},
multihash::Multihash,
peer_id::PeerId,
Error, Result,
};
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum Version {
Zero,
One,
Two,
Three,
}
#[derive(Clone, Eq, PartialEq)]
pub enum Cid {
Zero(Multihash),
One(Base, Multicodec, Multihash),
}
impl fmt::Display for Cid {
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
match self {
Cid::Zero(mh) => {
let base = Base::Base58Btc;
write!(f, "{:?}-cidv0-dag-pb-{}", base, mh)
}
Cid::One(base, codec, mh) => {
let cid_v1: Multicodec = multicodec::CID_V1.into();
write!(f, "{:?}-{}-{}-{}", base, cid_v1, codec, mh)
}
}
}
}
impl fmt::Debug for Cid {
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
<Cid as fmt::Display>::fmt(self, f)
}
}
impl FromStr for Cid {
type Err = Error;
fn from_str(s: &str) -> Result<Cid> {
Cid::from_text(s)
}
}
impl Cid {
pub fn new_v0(data: &[u8]) -> Result<Cid> {
let mh = Multihash::new(multicodec::SHA2_256.into(), data)?;
Ok(Cid::Zero(mh))
}
pub fn new_v1(base: Base, codec: Multicodec, data: &[u8]) -> Result<Cid> {
let mh = Multihash::new(multicodec::SHA2_256.into(), data)?;
Ok(Cid::One(base, codec, mh))
}
pub fn into_v1(self) -> Self {
use multibase::Base::Base58Btc;
match self {
Cid::Zero(peer_id) => {
let codec: Multicodec = multicodec::DAG_PB.into();
Cid::One(Base58Btc, codec, peer_id)
}
val @ Cid::One(_, _, _) => val,
}
}
pub fn from_peer_id_v0(peer_id: PeerId) -> Self {
Cid::Zero(peer_id.into())
}
pub fn from_peer_id_v1(base: Base, peer_id: PeerId) -> Self {
let code = multicodec::LIBP2P_KEY;
Cid::One(base, code.into(), peer_id.into())
}
pub fn from_text(text: &str) -> Result<Cid> {
let mut chars = text.chars();
let cid = match (chars.next(), chars.next()) {
(Some('Q'), Some('m')) | (Some('1'), Some(_)) => {
let bytes = {
let res = bs58::decode(text.as_bytes()).into_vec();
err_at!(DecodeError, res)?
};
let (mh, _) = Multihash::decode(&bytes)?;
Cid::Zero(mh)
}
_ => {
let (base, bytes) = {
let mb = Multibase::decode(text)?;
match mb.to_bytes() {
Some(bytes) => (mb.to_base(), bytes),
None => err_at!(DecodeError, msg: format!("{}", text))?,
}
};
let (codec, bytes) = Multicodec::decode(&bytes)?;
match codec.to_code() {
multicodec::CID_V1 => (),
_ => err_at!(DecodeError, msg: format!("CID {}", codec))?,
}
let (codec, bytes) = Multicodec::decode(bytes)?;
let (mh, _) = Multihash::decode(bytes)?;
Cid::One(base, codec, mh)
}
};
Ok(cid)
}
pub fn to_base_text(&self) -> Result<String> {
let text = match self {
Cid::Zero(mh) => bs58::encode(mh.encode()?).into_string(),
Cid::One(base, codec, mh) => {
let mut data = {
let codec = Multicodec::from_code(multicodec::CID_V1)?;
codec.encode()?
};
data.extend(codec.encode()?);
data.extend(mh.encode()?);
Multibase::from_base(base.clone(), &data)?.encode()?
}
};
Ok(text)
}
pub fn decode(bytes: &[u8]) -> Result<Cid> {
use multibase::Base::Base32Lower;
let cid = match bytes {
[0x12, 0x20, ..] => {
let (mh, _) = Multihash::decode(&bytes)?;
Cid::Zero(mh)
}
_ => {
let (codec, bytes) = Multicodec::decode(&bytes)?;
match codec.to_code() {
multicodec::CID_V1 => (),
_ => err_at!(DecodeError, msg: format!("CID {}", codec))?,
}
let (codec, bytes) = Multicodec::decode(bytes)?;
let (mh, _) = Multihash::decode(bytes)?;
Cid::One(Base32Lower, codec, mh)
}
};
Ok(cid)
}
pub fn encode(&self) -> Result<Vec<u8>> {
let bytes = match self {
Cid::Zero(mh) => mh.encode()?,
Cid::One(_, codec, mh) => {
let mut bytes = {
let codec = Multicodec::from_code(multicodec::CID_V1)?;
codec.encode()?
};
bytes.extend(codec.encode()?);
bytes.extend(mh.encode()?);
bytes
}
};
Ok(bytes)
}
pub fn to_version(&self) -> Version {
match self {
Cid::Zero(_) => Version::Zero,
Cid::One(_, _, _) => Version::One,
}
}
pub fn to_base(&self) -> Base {
match self {
Cid::Zero(_) => Base::Base58Btc,
Cid::One(base, _, _) => base.clone(),
}
}
pub fn to_content_type(&self) -> Multicodec {
match self {
Cid::Zero(_) => multicodec::DAG_PB.into(),
Cid::One(_, codec, _) => codec.clone(),
}
}
pub fn to_multihash(&self) -> Multihash {
match self {
Cid::Zero(mh) => mh.clone(),
Cid::One(_, _, mh) => mh.clone(),
}
}
pub fn to_peer_id(&self) -> Option<PeerId> {
let code = multicodec::LIBP2P_KEY;
match self {
Cid::One(_, codec, mh) if codec.to_code() == code => {
Some(mh.clone().into())
}
_ => None,
}
}
}
#[cfg(test)]
#[path = "cid_test.rs"]
mod cid_test;