use anyhow::Result;
use postcard::experimental::max_size::MaxSize;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::{fmt, result, str::FromStr};
use thiserror::Error;
pub mod io;
pub mod runtime;
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub struct Hash(blake3::Hash);
impl Hash {
pub fn new(buf: impl AsRef<[u8]>) -> Self {
let val = blake3::hash(buf.as_ref());
Hash(val)
}
pub fn as_bytes(&self) -> &[u8; 32] {
self.0.as_bytes()
}
pub fn as_cid_bytes(&self) -> [u8; 36] {
let hash = self.0.as_bytes();
let mut res = [0u8; 36];
res[0..4].copy_from_slice(&CID_PREFIX);
res[4..36].copy_from_slice(hash);
res
}
pub fn from_cid_bytes(bytes: &[u8]) -> anyhow::Result<Self> {
anyhow::ensure!(
bytes.len() == 36,
"invalid cid length, expected 36, got {}",
bytes.len()
);
anyhow::ensure!(bytes[0..4] == CID_PREFIX, "invalid cid prefix");
let mut hash = [0u8; 32];
hash.copy_from_slice(&bytes[4..36]);
Ok(Self::from(hash))
}
pub fn to_hex(&self) -> String {
self.0.to_hex().to_string()
}
}
impl AsRef<[u8]> for Hash {
fn as_ref(&self) -> &[u8] {
self.0.as_bytes()
}
}
impl From<Hash> for blake3::Hash {
fn from(value: Hash) -> Self {
value.0
}
}
impl From<blake3::Hash> for Hash {
fn from(value: blake3::Hash) -> Self {
Hash(value)
}
}
impl From<[u8; 32]> for Hash {
fn from(value: [u8; 32]) -> Self {
Hash(blake3::Hash::from(value))
}
}
impl PartialOrd for Hash {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.0.as_bytes().cmp(other.0.as_bytes()))
}
}
impl Ord for Hash {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.as_bytes().cmp(other.0.as_bytes())
}
}
impl fmt::Display for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut res = [b'b'; 59];
data_encoding::BASE32_NOPAD.encode_mut(&self.as_cid_bytes(), &mut res[1..]);
let t = std::str::from_utf8_mut(res.as_mut()).unwrap();
t.make_ascii_lowercase();
f.write_str(t)
}
}
impl FromStr for Hash {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let sb = s.as_bytes();
if sb.len() == 59 && sb[0] == b'b' {
let mut t = [0u8; 58];
t.copy_from_slice(&sb[1..]);
std::str::from_utf8_mut(t.as_mut())
.unwrap()
.make_ascii_uppercase();
let mut res = [0u8; 36];
data_encoding::BASE32_NOPAD
.decode_mut(&t, &mut res)
.map_err(|_e| anyhow::anyhow!("invalid base32"))?;
Self::from_cid_bytes(&res)
} else {
let (_base, bytes) = multibase::decode(s)?;
Self::from_cid_bytes(bytes.as_ref())
}
}
}
impl Serialize for Hash {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(self.0.as_bytes())
}
}
impl<'de> Deserialize<'de> for Hash {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_bytes(HashVisitor)
}
}
struct HashVisitor;
impl<'de> de::Visitor<'de> for HashVisitor {
type Value = Hash;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "an array of 32 bytes containing hash data")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
let bytes: [u8; 32] = v.try_into().map_err(E::custom)?;
Ok(Hash::from(bytes))
}
}
impl MaxSize for Hash {
const POSTCARD_MAX_SIZE: usize = 32;
}
const CID_PREFIX: [u8; 4] = [
0x01, 0x55, 0x1e, 0x20, ];
#[derive(Serialize, Deserialize, Debug, Error)]
pub struct RpcError(serde_error::Error);
impl fmt::Display for RpcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
impl From<anyhow::Error> for RpcError {
fn from(e: anyhow::Error) -> Self {
RpcError(serde_error::Error::new(&*e))
}
}
#[allow(dead_code)]
pub type RpcResult<T> = result::Result<T, RpcError>;
#[derive(Debug)]
pub(crate) struct NonSend {
_marker: std::marker::PhantomData<std::rc::Rc<()>>,
}
impl NonSend {
#[allow(dead_code)]
pub const fn new() -> Self {
Self {
_marker: std::marker::PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash() {
let data = b"hello world";
let hash = Hash::new(data);
let encoded = hash.to_string();
assert_eq!(encoded.parse::<Hash>().unwrap(), hash);
}
}