use bs58;
use multibase::Base;
use rand::Rng;
use std::{fmt, hash};
use crate::{
identity::PublicKey,
multibase::Multibase,
multicodec::{self, Multicodec},
multihash::Multihash,
Error, Result,
};
const MAX_INLINE_KEY_LENGTH: usize = 42;
#[derive(Clone, Eq)]
pub struct PeerId {
mh: Multihash,
}
impl fmt::Debug for PeerId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("PeerId").field(&self.to_base58btc()).finish()
}
}
impl fmt::Display for PeerId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.to_base58btc() {
Ok(val) => val.fmt(f),
Err(_) => Err(fmt::Error),
}
}
}
impl hash::Hash for PeerId {
fn hash<H>(&self, state: &mut H)
where
H: hash::Hasher,
{
hash::Hash::hash(&self.mh.encode().unwrap(), state)
}
}
impl PartialEq<PeerId> for PeerId {
fn eq(&self, other: &PeerId) -> bool {
self.mh == other.mh
}
}
impl From<Multihash> for PeerId {
fn from(mh: Multihash) -> Self {
PeerId { mh }
}
}
impl From<PeerId> for Multihash {
fn from(peer_id: PeerId) -> Self {
peer_id.mh
}
}
impl PeerId {
pub fn from_public_key(key: PublicKey) -> Result<PeerId> {
let enc_buf = key.into_protobuf_encoding()?;
let codec: Multicodec = match enc_buf.len() <= MAX_INLINE_KEY_LENGTH {
true => multicodec::IDENTITY.into(),
false => multicodec::SHA2_256.into(),
};
let mh = Multihash::new(codec, &enc_buf)?;
Ok(PeerId { mh })
}
pub fn generate() -> Result<PeerId> {
let (bytes, codec) = match rand::thread_rng().gen::<bool>() {
true => {
let codec: Multicodec = multicodec::IDENTITY.into();
let bytes = rand::thread_rng().gen::<[u8; 32]>().to_vec();
(bytes, codec)
}
false => {
let codec: Multicodec = multicodec::SHA2_256.into();
let bytes = {
let mut data = vec![];
data.extend(&rand::thread_rng().gen::<[u8; 32]>());
data.extend(&rand::thread_rng().gen::<[u8; 32]>());
data
};
(bytes, codec)
}
};
let mh = Multihash::new(codec, &bytes)?;
Ok(PeerId { mh })
}
pub fn from_text(text: &str) -> Result<PeerId> {
let mut chars = text.chars();
let peer_id = 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!(BadInput, res)?
};
let (mh, _) = Multihash::decode(&bytes)?;
PeerId { mh }
}
_ => {
let bytes = {
let mb = Multibase::decode(text)?;
match mb.to_bytes() {
Some(bytes) => bytes,
None => err_at!(BadInput, msg: format!("{}", text))?,
}
};
let (codec, bytes) = Multicodec::decode(&bytes)?;
match codec.to_code() {
multicodec::CID_V1 => (),
_ => err_at!(BadInput, msg: format!("CID {}", codec))?,
}
let (codec, bytes) = Multicodec::decode(bytes)?;
match codec.to_code() {
multicodec::LIBP2P_KEY => (),
_ => err_at!(BadInput, msg: format!("codec {}", codec))?,
}
let (mh, _) = Multihash::decode(bytes)?;
PeerId { mh }
}
};
Ok(peer_id)
}
pub fn to_base58btc(&self) -> Result<String> {
Ok(bs58::encode(self.mh.encode()?).into_string())
}
pub fn to_base_text(&self, base: Base) -> Result<String> {
let mut data = {
let codec = Multicodec::from_code(multicodec::CID_V1)?;
codec.encode()?
};
{
let codec = Multicodec::from_code(multicodec::LIBP2P_KEY)?;
data.extend(codec.encode()?);
};
data.extend(self.mh.encode()?);
Ok(Multibase::from_base(base.clone(), &data)?.encode()?)
}
pub fn encode(&self) -> Result<Vec<u8>> {
self.mh.encode()
}
pub fn decode(buf: &[u8]) -> Result<(PeerId, &[u8])> {
let (mh, rem) = Multihash::decode(buf)?;
Ok((PeerId { mh }, rem))
}
pub fn is_public_key(&self, public_key: &PublicKey) -> Option<bool> {
let other = PeerId::from_public_key(public_key.clone()).ok()?;
Some(self.mh == other.mh)
}
pub fn to_short_string(&self) -> String {
use std::iter::FromIterator;
let s = self.to_string();
let chars: Vec<char> = s.chars().collect();
if chars.len() <= 10 {
String::from_iter(chars.into_iter())
} else {
let mut short = chars[..2].to_vec();
short.push('*');
short.extend_from_slice(&chars[(chars.len() - 6)..]);
String::from_iter(short.into_iter())
}
}
pub fn to_public_key(&self) -> Result<Option<PublicKey>> {
let (codec, digest) = self.mh.clone().unwrap()?;
let public_key = match codec.to_code() {
multicodec::IDENTITY => {
let public_key = PublicKey::from_protobuf_encoding(&digest)?;
Some(public_key)
}
_ => None,
};
Ok(public_key)
}
}
#[cfg(test)]
#[path = "peer_id_test.rs"]
mod peer_id_test;